1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-15 01:04:37 +02:00

Refactor PgClient to return results in Pack format.

Packs support stronger typing than JSON and are more efficient. For the small result sets that we deal with efficiency is probably not very important, but this removes another place where we are using JSON instead of Pack.

Push checking for result struct (e.g. single row) down into PgClient since it has easy access to this information rather than needing to parse the result set to find out.

Refactor all code downstream that depends on PgClient results.
This commit is contained in:
David Steele
2022-04-20 08:36:53 -04:00
committed by GitHub
parent e699402f99
commit c304fafd45
12 changed files with 491 additions and 195 deletions

View File

@ -172,6 +172,22 @@
<p>Lock module refactoring.</p>
</release-item>
<release-item>
<commit subject="Update postgres/client unit test for changes in libpq."/>
<commit subject="Update postgres/client unit test to conform to current patterns."/>
<commit subject="Use specific integer types in postgres/client and db unit tests."/>
<commit subject="Refactor PgClient to return results in Pack format.">
<github-pull-request id="1718"/>
</commit>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
</release-item-contributor-list>
<p>Refactor <code>PgClient</code> to return results in <code>Pack</code> format.</p>
</release-item>
</release-development-list>
</release-core-list>

View File

@ -814,8 +814,8 @@ typedef struct BackupStartResult
{
String *lsn;
String *walSegmentName;
VariantList *dbList;
VariantList *tablespaceList;
Pack *dbList;
Pack *tablespaceList;
} BackupStartResult;
static BackupStartResult

View File

@ -100,35 +100,46 @@ dbNew(PgClient *client, ProtocolClient *remoteClient, const Storage *const stora
/***********************************************************************************************************************************
Execute a query
***********************************************************************************************************************************/
static VariantList *
dbQuery(Db *this, const String *query)
static Pack *
dbQuery(Db *this, const PgClientQueryResult resultType, const String *const query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
FUNCTION_LOG_PARAM(STRING_ID, resultType);
FUNCTION_LOG_PARAM(STRING, query);
FUNCTION_LOG_END();
ASSERT(this != NULL);
ASSERT(resultType != 0);
ASSERT(query != NULL);
VariantList *result = NULL;
Pack *result = NULL;
// Query remotely
if (this->remoteClient != NULL)
{
MEM_CONTEXT_TEMP_BEGIN()
{
ProtocolCommand *command = protocolCommandNew(PROTOCOL_COMMAND_DB_QUERY);
PackWrite *const param = protocolCommandParam(command);
pckWriteU32P(param, this->remoteIdx);
pckWriteStrIdP(param, resultType);
pckWriteStrP(param, query);
result = varVarLst(jsonToVar(pckReadStrP(protocolClientExecute(this->remoteClient, command, true))));
MEM_CONTEXT_PRIOR_BEGIN()
{
result = pckReadPackP(protocolClientExecute(this->remoteClient, command, true));
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
}
// Else locally
else
result = pgClientQuery(this->client, query);
result = pgClientQuery(this->client, query, resultType);
FUNCTION_LOG_RETURN(VARIANT_LIST, result);
FUNCTION_LOG_RETURN(PACK, result);
}
/***********************************************************************************************************************************
@ -145,7 +156,7 @@ dbExec(Db *this, const String *command)
ASSERT(this != NULL);
ASSERT(command != NULL);
CHECK(AssertError, dbQuery(this, command) == NULL, "exec returned data");
CHECK(AssertError, dbQuery(this, pgClientQueryResultNone, command) == NULL, "exec returned data");
FUNCTION_LOG_RETURN_VOID();
}
@ -153,8 +164,8 @@ dbExec(Db *this, const String *command)
/***********************************************************************************************************************************
Execute a query that returns a single row and column
***********************************************************************************************************************************/
static Variant *
dbQueryColumn(Db *this, const String *query)
static PackRead *
dbQueryColumn(Db *const this, const String *const query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
@ -164,19 +175,14 @@ dbQueryColumn(Db *this, const String *query)
ASSERT(this != NULL);
ASSERT(query != NULL);
VariantList *result = dbQuery(this, query);
CHECK(AssertError, varLstSize(result) == 1, "query must return one column");
CHECK(AssertError, varLstSize(varVarLst(varLstGet(result, 0))) == 1, "query must return one row");
FUNCTION_LOG_RETURN(VARIANT, varLstGet(varVarLst(varLstGet(result, 0)), 0));
FUNCTION_LOG_RETURN(PACK_READ, pckReadNew(dbQuery(this, pgClientQueryResultColumn, query)));
}
/***********************************************************************************************************************************
Execute a query that returns a single row
***********************************************************************************************************************************/
static VariantList *
dbQueryRow(Db *this, const String *query)
static PackRead *
dbQueryRow(Db *const this, const String *const query)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(DB, this);
@ -186,11 +192,7 @@ dbQueryRow(Db *this, const String *query)
ASSERT(this != NULL);
ASSERT(query != NULL);
VariantList *result = dbQuery(this, query);
CHECK(AssertError, varLstSize(result) == 1, "query must return one row");
FUNCTION_LOG_RETURN(VARIANT_LIST, varVarLst(varLstGet(result, 0)));
FUNCTION_LOG_RETURN(PACK_READ, pckReadNew(dbQuery(this, pgClientQueryResultRow, query)));
}
/***********************************************************************************************************************************
@ -208,7 +210,7 @@ dbIsInRecovery(Db *const this)
bool result = false;
if (dbPgVersion(this) >= PG_VERSION_HOT_STANDBY)
result = varBool(dbQueryColumn(this, STRDEF("select pg_catalog.pg_is_in_recovery()")));
result = pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_is_in_recovery()")));
FUNCTION_LOG_RETURN(BOOL, result);
}
@ -251,9 +253,9 @@ dbOpen(Db *this)
// Set client encoding to UTF8. This is the only encoding (other than ASCII) that we can safely work with.
dbExec(this, STRDEF("set client_encoding = 'UTF8'"));
// Query the version and data_directory
VariantList *row = dbQueryRow(
this,
// Query the version and data_directory. Be sure the update the total in the null check below when adding/removing columns.
const Pack *const row = dbQuery(
this, pgClientQueryResultRow,
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,"
@ -262,9 +264,11 @@ dbOpen(Db *this)
" (select setting from pg_catalog.pg_settings where name = 'checkpoint_timeout')::int4"));
// Check that none of the return values are null, which indicates the user cannot select some rows in pg_settings
for (unsigned int columnIdx = 0; columnIdx < varLstSize(row); columnIdx++)
PackRead *read = pckReadNew(row);
for (unsigned int columnIdx = 0; columnIdx < 5; columnIdx++)
{
if (varLstGet(row, columnIdx) == NULL)
if (pckReadNullP(read, .id = columnIdx + 1))
{
THROW(
DbQueryError,
@ -274,19 +278,22 @@ dbOpen(Db *this)
}
}
// Restart the read to get the data
read = pckReadNew(row);
// 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->pub.pgVersion = varUIntForce(varLstGet(row, 0)) / 100 * 100;
this->pub.pgVersion = (unsigned int)pckReadI32P(read) / 100 * 100;
// Store the data directory that PostgreSQL is running in, the archive mode, and archive command. These can be compared to
// the configured pgBackRest directory, and archive settings checked for validity, when validating the configuration.
// Also store the checkpoint timeout to warn in case a backup is requested without using the start-fast option.
MEM_CONTEXT_BEGIN(this->pub.memContext)
{
this->pub.pgDataPath = strDup(varStr(varLstGet(row, 1)));
this->pub.archiveMode = strDup(varStr(varLstGet(row, 2)));
this->pub.archiveCommand = strDup(varStr(varLstGet(row, 3)));
this->pub.checkpointTimeout = (TimeMSec)(varUIntForce(varLstGet(row, 4))) * MSEC_PER_SEC;
this->pub.pgDataPath = pckReadStrP(read);
this->pub.archiveMode = pckReadStrP(read);
this->pub.archiveCommand = pckReadStrP(read);
this->pub.checkpointTimeout = (TimeMSec)pckReadI32P(read) * MSEC_PER_SEC;
}
MEM_CONTEXT_END();
@ -364,7 +371,7 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
{
// 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"))))
if (!pckReadBoolP(dbQueryColumn(this, STRDEF("select pg_catalog.pg_try_advisory_lock(" PG_BACKUP_ADVISORY_LOCK ")::bool"))))
{
THROW(
LockAcquireError,
@ -380,7 +387,7 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
// Feature is not needed for PostgreSQL >= 9.6 since backups are run in non-exclusive mode
if (dbPgVersion(this) < PG_VERSION_96)
{
if (varBool(dbQueryColumn(this, STRDEF("select pg_catalog.pg_is_in_backup()::bool"))))
if (pckReadBoolP(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."
@ -406,7 +413,7 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
if (archiveCheck)
{
walSegmentCheck = varStr(
walSegmentCheck = pckReadStrP(
dbQueryColumn(
this,
strNewFmt(
@ -416,12 +423,12 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
}
// Start backup
VariantList *row = dbQueryRow(this, dbBackupStartQuery(dbPgVersion(this), startFast));
PackRead *const read = dbQueryRow(this, dbBackupStartQuery(dbPgVersion(this), startFast));
// Make sure the backup start checkpoint was written to pg_control. This helps ensure that we have a consistent view of the
// storage with PostgreSQL.
const PgControl pgControl = pgControlFromFile(this->storage);
const String *const lsnStart = varStr(varLstGet(row, 0));
const String *const lsnStart = pckReadStrP(read);
if (pgControl.checkpoint < pgLsnFromStr(lsnStart))
{
@ -431,7 +438,7 @@ dbBackupStart(Db *const this, const bool startFast, const bool stopAuto, const b
}
// If archive check then make sure WAL segment was switched on start backup
const String *const walSegmentName = varStr(varLstGet(row, 1));
const String *const walSegmentName = pckReadStrP(read);
if (archiveCheck && strEq(walSegmentCheck, walSegmentName))
{
@ -530,24 +537,28 @@ dbBackupStop(Db *this)
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;
PackRead *const read = dbQueryRow(this, dbBackupStopQuery(dbPgVersion(this)));
// Return results
MEM_CONTEXT_PRIOR_BEGIN()
{
result.lsn = strDup(varStr(varLstGet(row, 0)));
result.walSegmentName = strDup(varStr(varLstGet(row, 1)));
result.lsn = pckReadStrP(read);
result.walSegmentName = pckReadStrP(read);
if (dbPgVersion(this) >= PG_VERSION_96)
{
result.backupLabel = strDup(varStr(varLstGet(row, 2)));
result.backupLabel = pckReadStrP(read);
if (!tablespaceMapEmpty)
result.tablespaceMap = strDup(varStr(varLstGet(row, 3)));
// Return the tablespace map if it is not empty
String *const tablespaceMap = pckReadStrP(read);
String *const tablespaceMapTrim = strTrim(strDup(tablespaceMap));
if (!strEmpty(tablespaceMapTrim))
result.tablespaceMap = tablespaceMap;
else
strFree(tablespaceMap);
strFree(tablespaceMapTrim);
}
}
MEM_CONTEXT_PRIOR_END();
@ -558,7 +569,7 @@ dbBackupStop(Db *this)
}
/**********************************************************************************************************************************/
VariantList *
Pack *
dbList(Db *this)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
@ -566,7 +577,10 @@ dbList(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")));
PACK,
dbQuery(
this, pgClientQueryResultAny,
STRDEF("select oid::oid, datname::text, datlastsysoid::oid from pg_catalog.pg_database")));
}
/**********************************************************************************************************************************/
@ -633,8 +647,8 @@ dbReplayWait(Db *const this, const String *const targetLsn, const uint32_t targe
strZ(replayLsnFunction));
// Execute the query and get replayLsn
VariantList *row = dbQueryRow(this, query);
replayLsn = varStr(varLstGet(row, 0));
PackRead *read = dbQueryRow(this, query);
replayLsn = pckReadStrP(read);
// 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 standby was promoted in the meantime.
@ -647,10 +661,10 @@ dbReplayWait(Db *const this, const String *const targetLsn, const uint32_t targe
strZ(replayLsnFunction));
}
targetReached = varBool(varLstGet(row, 1));
targetReached = pckReadBoolP(read);
// 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)))
if (!targetReached && pckReadBoolP(read, .defaultValue = true))
wait = waitNew(timeout);
protocolKeepAlive();
@ -688,9 +702,9 @@ dbReplayWait(Db *const this, const String *const targetLsn, const uint32_t targe
lsnName, strZ(targetLsn), lsnName);
// Execute the query and get checkpointLsn
VariantList *row = dbQueryRow(this, query);
targetReached = varBool(varLstGet(row, 0));
checkpointLsn = varStr(varLstGet(row, 1));
PackRead *const read = dbQueryRow(this, query);
targetReached = pckReadBoolP(read);
checkpointLsn = pckReadStrP(read);
protocolKeepAlive();
}
@ -753,14 +767,16 @@ dbPing(Db *const this, const bool force)
}
/**********************************************************************************************************************************/
VariantList *
Pack *
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")));
FUNCTION_LOG_RETURN(
PACK,
dbQuery(this, pgClientQueryResultAny, STRDEF("select oid::oid, spcname::text from pg_catalog.pg_tablespace")));
}
/**********************************************************************************************************************************/
@ -772,7 +788,8 @@ dbTimeMSec(Db *this)
FUNCTION_LOG_END();
FUNCTION_LOG_RETURN(
TIME_MSEC, varUInt64Force(dbQueryColumn(this, STRDEF("select (extract(epoch from clock_timestamp()) * 1000)::bigint"))));
TIME_MSEC,
(TimeMSec)pckReadI64P(dbQueryColumn(this, STRDEF("select (extract(epoch from clock_timestamp()) * 1000)::bigint"))));
}
/**********************************************************************************************************************************/
@ -796,7 +813,7 @@ dbWalSwitch(Db *this)
// Request a WAL segment switch
const char *walName = strZ(pgWalName(dbPgVersion(this)));
const String *walFileName = varStr(
const String *walFileName = pckReadStrP(
dbQueryColumn(this, strNewFmt("select pg_catalog.pg_%sfile_name(pg_catalog.pg_switch_%s())::text", walName, walName)));
// Copy WAL segment name to the prior context

View File

@ -123,7 +123,7 @@ typedef struct DbBackupStopResult
DbBackupStopResult dbBackupStop(Db *this);
// Get list of databases in the cluster: select oid, datname, datlastsysoid from pg_database
VariantList *dbList(Db *this);
Pack *dbList(Db *this);
// Waits for replay on the standby to equal the target LSN
void dbReplayWait(Db *this, const String *targetLsn, uint32_t targetTimeline, TimeMSec timeout);
@ -135,7 +135,7 @@ void dbPing(Db *const this, bool force);
TimeMSec dbTimeMSec(Db *this);
// Get list of tablespaces in the cluster: select oid, datname, datlastsysoid from pg_database
VariantList *dbTablespaceList(Db *this);
Pack *dbTablespaceList(Db *this);
// Switch the WAL segment and return the segment that should have been archived
String *dbWalSwitch(Db *this);

View File

@ -83,9 +83,10 @@ dbQueryProtocol(PackRead *const param, ProtocolServer *const server)
MEM_CONTEXT_TEMP_BEGIN()
{
PgClient *const pgClient = *(PgClient **)lstGet(dbProtocolLocal.pgClientList, pckReadU32P(param));
const PgClientQueryResult resultType = (PgClientQueryResult)pckReadStrIdP(param);
const String *const query = pckReadStrP(param);
protocolServerDataPut(server, pckWriteStrP(protocolPackNew(), jsonFromVar(varNewVarLst(pgClientQuery(pgClient, query)))));
protocolServerDataPut(server, pckWritePackP(protocolPackNew(), pgClientQuery(pgClient, query, resultType)));
protocolServerDataEndPut(server);
}
MEM_CONTEXT_TEMP_END();

View File

@ -806,7 +806,7 @@ typedef struct ManifestBuildData
const String *manifestWalName; // Wal manifest name for this version of PostgreSQL
RegExp *dbPathExp; // Identify paths containing relations
RegExp *tempRelationExp; // Identify temp relations
const VariantList *tablespaceList; // List of tablespaces in the database
const Pack *tablespaceList; // List of tablespaces in the database
ManifestLinkCheck linkCheck; // List of links found during build (used for prefix check)
StringList *excludeContent; // Exclude contents of directories
StringList *excludeSingle; // Exclude a single file/link/path
@ -1115,12 +1115,16 @@ manifestBuildCallback(void *data, const StorageInfo *info)
if (buildData.tablespaceList != NULL)
{
// Search list
for (unsigned int tablespaceIdx = 0; tablespaceIdx < varLstSize(buildData.tablespaceList); tablespaceIdx++)
{
const VariantList *tablespace = varVarLst(varLstGet(buildData.tablespaceList, tablespaceIdx));
PackRead *const read = pckReadNew(buildData.tablespaceList);
if (target.tablespaceId == varUIntForce(varLstGet(tablespace, 0)))
target.tablespaceName = varStr(varLstGet(tablespace, 1));
while (!pckReadNullP(read))
{
pckReadArrayBeginP(read);
if (target.tablespaceId == pckReadU32P(read))
target.tablespaceName = pckReadStrP(read);
pckReadArrayEndP(read);
}
// Error if the tablespace could not be found. ??? This seems excessive, perhaps just warn here?
@ -1244,7 +1248,7 @@ manifestBuildCallback(void *data, const StorageInfo *info)
Manifest *
manifestNewBuild(
const Storage *const storagePg, const unsigned int pgVersion, const unsigned int pgCatalogVersion, const bool online,
const bool checksumPage, const bool bundle, const StringList *const excludeList, const VariantList *const tablespaceList)
const bool checksumPage, const bool bundle, const StringList *const excludeList, const Pack *const tablespaceList)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STORAGE, storagePg);
@ -1254,7 +1258,7 @@ manifestNewBuild(
FUNCTION_LOG_PARAM(BOOL, checksumPage);
FUNCTION_LOG_PARAM(BOOL, bundle);
FUNCTION_LOG_PARAM(STRING_LIST, excludeList);
FUNCTION_LOG_PARAM(VARIANT_LIST, tablespaceList);
FUNCTION_LOG_PARAM(PACK, tablespaceList);
FUNCTION_LOG_END();
ASSERT(storagePg != NULL);
@ -1629,11 +1633,11 @@ manifestBuildIncr(Manifest *this, const Manifest *manifestPrior, BackupType type
/**********************************************************************************************************************************/
void
manifestBuildComplete(
Manifest *this, time_t timestampStart, const String *lsnStart, const String *archiveStart, time_t timestampStop,
const String *lsnStop, const String *archiveStop, unsigned int pgId, uint64_t pgSystemId, const VariantList *dbList,
bool optionArchiveCheck, bool optionArchiveCopy, size_t optionBufferSize, unsigned int optionCompressLevel,
unsigned int optionCompressLevelNetwork, bool optionHardLink, unsigned int optionProcessMax,
bool optionStandby)
Manifest *const this, const time_t timestampStart, const String *const lsnStart, const String *const archiveStart,
const time_t timestampStop, const String *const lsnStop, const String *const archiveStop, const unsigned int pgId,
const uint64_t pgSystemId, const Pack *const dbList, const bool optionArchiveCheck, const bool optionArchiveCopy,
const size_t optionBufferSize, const unsigned int optionCompressLevel, const unsigned int optionCompressLevelNetwork,
const bool optionHardLink, const unsigned int optionProcessMax, const bool optionStandby)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(MANIFEST, this);
@ -1645,7 +1649,7 @@ manifestBuildComplete(
FUNCTION_LOG_PARAM(STRING, archiveStop);
FUNCTION_LOG_PARAM(UINT, pgId);
FUNCTION_LOG_PARAM(UINT64, pgSystemId);
FUNCTION_LOG_PARAM(VARIANT_LIST, dbList);
FUNCTION_LOG_PARAM(PACK, dbList);
FUNCTION_LOG_PARAM(BOOL, optionArchiveCheck);
FUNCTION_LOG_PARAM(BOOL, optionArchiveCopy);
FUNCTION_LOG_PARAM(SIZE, optionBufferSize);
@ -1671,18 +1675,20 @@ manifestBuildComplete(
// Save db list
if (dbList != NULL)
{
for (unsigned int dbIdx = 0; dbIdx < varLstSize(dbList); dbIdx++)
{
const VariantList *dbRow = varVarLst(varLstGet(dbList, dbIdx));
PackRead *const read = pckReadNew(dbList);
ManifestDb db =
while (!pckReadNullP(read))
{
.id = varUIntForce(varLstGet(dbRow, 0)),
.name = varStr(varLstGet(dbRow, 1)),
.lastSystemId = varUIntForce(varLstGet(dbRow, 2)),
};
pckReadArrayBeginP(read);
const unsigned int id = pckReadU32P(read);
const String *const name = pckReadStrP(read);
const unsigned int lastSystemId = pckReadU32P(read);
pckReadArrayEndP(read);
manifestDbAdd(this, &(ManifestDb){.id = id, .name = name, .lastSystemId = lastSystemId});
manifestDbAdd(this, &db);
}
lstSort(this->pub.dbList, sortOrderAsc);

View File

@ -33,7 +33,7 @@ typedef struct Manifest Manifest;
#include "common/compress/helper.h"
#include "common/crypto/common.h"
#include "common/crypto/hash.h"
#include "common/type/variantList.h"
#include "common/type/variant.h"
#include "common/type/object.h"
#include "info/info.h"
#include "info/infoBackup.h"
@ -158,7 +158,7 @@ Constructors
// Build a new manifest for a PostgreSQL data directory
Manifest *manifestNewBuild(
const Storage *storagePg, unsigned int pgVersion, unsigned int pgCatalogVersion, bool online, bool checksumPage, bool bundle,
const StringList *excludeList, const VariantList *tablespaceList);
const StringList *excludeList, const Pack *tablespaceList);
// Load a manifest from IO
Manifest *manifestNewLoad(IoRead *read);
@ -213,7 +213,7 @@ void manifestBuildIncr(Manifest *this, const Manifest *prior, BackupType type, c
// Set remaining values before the final save
void manifestBuildComplete(
Manifest *this, time_t timestampStart, const String *lsnStart, const String *archiveStart, time_t timestampStop,
const String *lsnStop, const String *archiveStop, unsigned int pgId, uint64_t pgSystemId, const VariantList *dbList,
const String *lsnStop, const String *archiveStop, unsigned int pgId, uint64_t pgSystemId, const Pack *dbList,
bool optionArchiveCheck, bool optionArchiveCopy, size_t optionBufferSize, unsigned int optionCompressLevel,
unsigned int optionCompressLevelNetwork, bool optionHardLink, unsigned int optionProcessMax, bool optionStandby);

View File

@ -7,7 +7,6 @@ Postgres Client
#include "common/debug.h"
#include "common/log.h"
#include "common/type/list.h"
#include "common/wait.h"
#include "postgres/client.h"
@ -166,19 +165,21 @@ pgClientOpen(PgClient *this)
}
/**********************************************************************************************************************************/
VariantList *
pgClientQuery(PgClient *this, const String *query)
Pack *
pgClientQuery(PgClient *const this, const String *const query, const PgClientQueryResult resultType)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(PG_CLIENT, this);
FUNCTION_LOG_PARAM(STRING, query);
FUNCTION_LOG_PARAM(STRING_ID, resultType);
FUNCTION_LOG_END();
ASSERT(this != NULL);
CHECK(AssertError, this->connection != NULL, "invalid connection");
ASSERT(query != NULL);
ASSERT(resultType != 0);
VariantList *result = NULL;
Pack *result = NULL;
MEM_CONTEXT_TEMP_BEGIN()
{
@ -236,8 +237,16 @@ pgClientQuery(PgClient *this, const String *query)
// If this was a command that returned no results then we are done
ExecStatusType resultStatus = PQresultStatus(pgResult);
if (resultStatus != PGRES_COMMAND_OK)
if (resultStatus == PGRES_COMMAND_OK)
{
if (resultType != pgClientQueryResultNone && resultType != pgClientQueryResultAny)
THROW_FMT(DbQueryError, "result expected from '%s'", strZ(query));
}
else
{
if (resultType == pgClientQueryResultNone)
THROW_FMT(DbQueryError, "no result expected from '%s'", strZ(query));
// Expect some rows to be returned
if (resultStatus != PGRES_TUPLES_OK)
{
@ -247,13 +256,20 @@ pgClientQuery(PgClient *this, const String *query)
}
// Fetch row and column values
result = varLstNew();
PackWrite *const pack = pckWriteNewP();
MEM_CONTEXT_BEGIN(lstMemContext((List *)result))
{
int rowTotal = PQntuples(pgResult);
int columnTotal = PQnfields(pgResult);
if (resultType != pgClientQueryResultAny)
{
if (rowTotal != 1)
THROW_FMT(DbQueryError, "expected one row from '%s'", strZ(query));
if (resultType == pgClientQueryResultColumn && columnTotal != 1)
THROW_FMT(DbQueryError, "expected one column from '%s'", strZ(query));
}
// Get column types
Oid *columnType = memNew(sizeof(int) * (size_t)columnTotal);
@ -263,7 +279,8 @@ pgClientQuery(PgClient *this, const String *query)
// Get values
for (int rowIdx = 0; rowIdx < rowTotal; rowIdx++)
{
VariantList *resultRow = varLstNew();
if (resultType == pgClientQueryResultAny)
pckWriteArrayBeginP(pack);
for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++)
{
@ -272,7 +289,7 @@ pgClientQuery(PgClient *this, const String *query)
// If value is zero-length then check if it is null
if (value[0] == '\0' && PQgetisnull(pgResult, rowIdx, columnIdx))
{
varLstAdd(resultRow, NULL);
pckWriteNullP(pack);
}
// Else convert the value to a variant
else
@ -282,21 +299,29 @@ pgClientQuery(PgClient *this, const String *query)
{
// Boolean type
case 16: // bool
varLstAdd(resultRow, varNewBool(varBoolForce(varNewStrZ(value))));
pckWriteBoolP(pack, varBoolForce(varNewStrZ(value)), .defaultWrite = true);
break;
// Text/char types
case 18: // char
case 19: // name
case 25: // text
varLstAdd(resultRow, varNewStrZ(value));
pckWriteStrP(pack, STR(value), .defaultWrite = true);
break;
// Integer types
// 64-bit integer type
case 20: // int8
pckWriteI64P(pack, cvtZToInt64(value), .defaultWrite = true);
break;
// 32-bit integer type
case 23: // int4
pckWriteI32P(pack, cvtZToInt(value), .defaultWrite = true);
break;
// 32-bit unsigned integer type
case 26: // oid
varLstAdd(resultRow, varNewInt64(cvtZToInt64(value)));
pckWriteU32P(pack, cvtZToUInt(value), .defaultWrite = true);
break;
default:
@ -307,10 +332,14 @@ pgClientQuery(PgClient *this, const String *query)
}
}
varLstAdd(result, varNewVarLst(resultRow));
if (resultType == pgClientQueryResultAny)
pckWriteArrayEndP(pack);
}
}
MEM_CONTEXT_END();
// End pack and return result
pckWriteEndP(pack);
result = pckMove(pckWriteResult(pack), memContextPrior());
}
}
FINALLY()
@ -321,12 +350,10 @@ pgClientQuery(PgClient *this, const String *query)
CHECK(ServiceError, PQgetResult(this->connection) == NULL, "NULL result required to complete request");
}
TRY_END();
varLstMove(result, memContextPrior());
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(VARIANT_LIST, result);
FUNCTION_LOG_RETURN(PACK, result);
}
/**********************************************************************************************************************************/

View File

@ -9,10 +9,21 @@ casts to queries to output one of these types.
#define POSTGRES_QUERY_H
#include "common/type/object.h"
#include "common/type/pack.h"
#include "common/type/string.h"
#include "common/type/variantList.h"
#include "common/time.h"
/***********************************************************************************************************************************
Query result types
***********************************************************************************************************************************/
typedef enum
{
pgClientQueryResultAny = STRID5("any", 0x65c10), // Any number of rows/columns expected (even none)
pgClientQueryResultRow = STRID5("row", 0x5df20), // One row expected
pgClientQueryResultColumn = STRID5("column", 0x1cdab1e30), // One row/column expected
pgClientQueryResultNone = STRID5("none", 0x2b9ee0), // No rows expected
} PgClientQueryResult;
/***********************************************************************************************************************************
Object type
***********************************************************************************************************************************/
@ -85,7 +96,7 @@ pgClientMove(PgClient *const this, MemContext *const parentNew)
}
// Execute a query and return results
VariantList *pgClientQuery(PgClient *this, const String *query);
Pack *pgClientQuery(PgClient *this, const String *query, PgClientQueryResult resultType);
// Close connection to PostgreSQL
void pgClientClose(PgClient *this);

View File

@ -9,6 +9,7 @@ Test Database
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
#include "common/harnessLog.h"
#include "common/harnessPack.h"
#include "common/harnessPostgres.h"
#include "common/harnessPq.h"
@ -338,8 +339,8 @@ testRun(void)
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");
TEST_RESULT_STR_Z(hrnPackToStr(dbList(db.primary)), "1:array:[1:u32:16384, 2:str:test1, 3:u32:13777]", "check db list");
TEST_RESULT_STR_Z(hrnPackToStr(dbTablespaceList(db.primary)), "", "check tablespace list");
DbBackupStopResult backupStopResult = {.lsn = NULL};
TEST_ASSIGN(backupStopResult, dbBackupStop(db.primary), "stop backup");

View File

@ -282,25 +282,35 @@ testRun(void)
.timeModified = 1565282115);
// Add tablespaceList with error (no name)
VariantList *tablespaceList = varLstNew();
VariantList *tablespace = varLstNew();
varLstAdd(tablespace, varNewUInt(2)); // tablespaceId - does not exist so will bypass
varLstAdd(tablespace, varNewStrZ("tblspc2")); // tablespaceName
varLstAdd(tablespaceList, varNewVarLst(tablespace));
PackWrite *tablespaceList = pckWriteNewP();
pckWriteArrayBeginP(tablespaceList);
pckWriteU32P(tablespaceList, 2);
pckWriteStrP(tablespaceList, STRDEF("tblspc2"));
pckWriteArrayEndP(tablespaceList);
pckWriteEndP(tablespaceList);
// Test tablespace error
TEST_ERROR(
manifestNewBuild(
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, exclusionList, tablespaceList),
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, exclusionList,
pckWriteResult(tablespaceList)),
AssertError,
"tablespace with oid 1 not found in tablespace map\n"
"HINT: was a tablespace created or dropped during the backup?");
// Add a tablespace to tablespaceList that does exist
tablespace = varLstNew();
varLstAdd(tablespace, varNewUInt(1)); // tablespaceId - exists so will show up in manifest with tablespaceName
varLstAdd(tablespace, varNewStrZ("tblspc1")); // tablespaceName
varLstAdd(tablespaceList, varNewVarLst(tablespace));
tablespaceList = pckWriteNewP();
pckWriteArrayBeginP(tablespaceList);
pckWriteU32P(tablespaceList, 2);
pckWriteStrP(tablespaceList, STRDEF("tblspc2"));
pckWriteArrayEndP(tablespaceList);
pckWriteArrayBeginP(tablespaceList);
pckWriteU32P(tablespaceList, 1);
pckWriteStrP(tablespaceList, STRDEF("tblspc1"));
pckWriteArrayEndP(tablespaceList);
pckWriteEndP(tablespaceList);
// Test manifest - temp tables and pg_notify files ignored
Manifest *manifest = NULL;
@ -308,7 +318,8 @@ testRun(void)
TEST_ASSIGN(
manifest,
manifestNewBuild(
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, NULL, tablespaceList),
storagePg, PG_VERSION_90, hrnPgCatalogVersion(PG_VERSION_90), false, false, false, NULL,
pckWriteResult(tablespaceList)),
"build manifest");
Buffer *contentSave = bufNew(0);
@ -1615,30 +1626,32 @@ testRun(void)
"manifest complete without db");
// Create db list
VariantList *dbList = varLstNew();
PackWrite *dbList = pckWriteNewP();
VariantList *dbRow = varLstNew();
varLstAdd(dbRow, varNewUInt64(12168));
varLstAdd(dbRow, varNewStrZ("template0"));
varLstAdd(dbRow, varNewUInt64(12168));
varLstAdd(dbList, varNewVarLst(dbRow));
pckWriteArrayBeginP(dbList);
pckWriteU32P(dbList, 12168);
pckWriteStrP(dbList, STRDEF("template0"));
pckWriteU32P(dbList, 12168);
pckWriteArrayEndP(dbList);
dbRow = varLstNew();
varLstAdd(dbRow, varNewUInt64(1));
varLstAdd(dbRow, varNewStrZ("template1"));
varLstAdd(dbRow, varNewUInt64(12168));
varLstAdd(dbList, varNewVarLst(dbRow));
pckWriteArrayBeginP(dbList);
pckWriteU32P(dbList, 1);
pckWriteStrP(dbList, STRDEF("template1"));
pckWriteU32P(dbList, 12168);
pckWriteArrayEndP(dbList);
dbRow = varLstNew();
varLstAdd(dbRow, varNewUInt64(18000));
varLstAdd(dbRow, varNewStrZ(SHRUG_EMOJI));
varLstAdd(dbRow, varNewUInt64(12168));
varLstAdd(dbList, varNewVarLst(dbRow));
pckWriteArrayBeginP(dbList);
pckWriteU32P(dbList, 18000);
pckWriteStrP(dbList, STRDEF(SHRUG_EMOJI));
pckWriteU32P(dbList, 12168);
pckWriteArrayEndP(dbList);
pckWriteEndP(dbList);
TEST_RESULT_VOID(
manifestBuildComplete(
manifest, 1565282140, STRDEF("285/89000028"), STRDEF("000000030000028500000089"), 1565282142,
STRDEF("285/89001F88"), STRDEF("000000030000028500000089"), 1, 1000000000000000094, dbList,
STRDEF("285/89001F88"), STRDEF("000000030000028500000089"), 1, 1000000000000000094, pckWriteResult(dbList),
true, true, 16384, 3, 6, true, 32, false),
"manifest complete with db");

View File

@ -10,6 +10,7 @@ define.yaml. THe PostgreSQL version can be adjusted by changing TEST_PG_VERSION.
***********************************************************************************************************************************/
#include "common/type/json.h"
#include "common/harnessPack.h"
#include "common/harnessPq.h"
/***********************************************************************************************************************************
@ -98,7 +99,8 @@ testRun(void)
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY)), DbQueryError, "unable to send query '" TEST_QUERY "': " TEST_PQ_ERROR);
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultRow), DbQueryError,
"unable to send query '" TEST_QUERY "': " TEST_PQ_ERROR);
TEST_RESULT_VOID(pgClientFree(client), "free client");
@ -147,7 +149,8 @@ testRun(void)
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY)), DbQueryError, "unable to execute query '" TEST_QUERY "': " TEST_PQ_ERROR);
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultRow), DbQueryError,
"unable to execute query '" TEST_QUERY "': " TEST_PQ_ERROR);
#undef TEST_PQ_ERROR
#undef TEST_QUERY
@ -175,7 +178,9 @@ testRun(void)
});
#endif
TEST_ERROR(pgClientQuery(client, STRDEF(TEST_QUERY)), DbQueryError, "query '" TEST_QUERY "' timed out after 500ms");
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn), DbQueryError,
"query '" TEST_QUERY "' timed out after 500ms");
#undef TEST_QUERY
@ -200,7 +205,8 @@ testRun(void)
});
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY)), DbQueryError, "unable to cancel query '" TEST_QUERY "': " TEST_PQ_ERROR);
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn), DbQueryError,
"unable to cancel query '" TEST_QUERY "': " TEST_PQ_ERROR);
#undef TEST_PQ_ERROR
#undef TEST_QUERY
@ -224,12 +230,85 @@ testRun(void)
});
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY)), DbQueryError,
"unable to cancel query '" TEST_QUERY "': connection was lost");
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn), DbQueryError,
"unable to cancel query 'select 1': connection was lost");
#undef TEST_QUERY
#endif
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error when results expected");
#define TEST_QUERY "set client_encoding = 'UTF8'"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_COMMAND_OK},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn), DbQueryError,
"result expected from '" TEST_QUERY "'");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error when no results expected");
#define TEST_QUERY "select * from pg_class limit 1"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultNone), DbQueryError,
"no result expected from '" TEST_QUERY "'");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("set with no results");
#define TEST_QUERY "set client_encoding = 'UTF8'"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_COMMAND_OK},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_RESULT_PTR(pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultAny), NULL, "execute set");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("execute do block and raise notice");
@ -249,7 +328,7 @@ testRun(void)
});
#endif
TEST_RESULT_PTR(pgClientQuery(client, STRDEF(TEST_QUERY)), NULL, "execute do block");
TEST_RESULT_PTR(pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultNone), NULL, "execute do block");
#undef TEST_QUERY
@ -277,7 +356,7 @@ testRun(void)
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY)), FormatError,
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultAny), FormatError,
"unable to parse type 1184 in column 0 for query '" TEST_QUERY "'");
#undef TEST_QUERY
@ -325,8 +404,133 @@ testRun(void)
#endif
TEST_RESULT_STR_Z(
jsonFromVar(varNewVarLst(pgClientQuery(client, STRDEF(TEST_QUERY)))),
"[[1259,null,\"pg_class\",true],[1255,\"\",\"pg_proc\",false]]", "simple query");
hrnPackToStr(pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultAny)),
"1:array:[1:u32:1259, 3:str:pg_class, 4:bool:true], 2:array:[1:u32:1255, 2:str:, 3:str:pg_proc, 4:bool:false]",
"simple query");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error when result is not a single row");
#define TEST_QUERY "select * from pg_class limit 2"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.function = HRNPQ_NTUPLES, .resultInt = 2},
{.function = HRNPQ_NFIELDS, .resultInt = 1},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultRow), DbQueryError,
"expected one row from '" TEST_QUERY "'");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("single row result");
#define TEST_QUERY "select 1259::oid, -9223372036854775807::int8"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.function = HRNPQ_NTUPLES, .resultInt = 1},
{.function = HRNPQ_NFIELDS, .resultInt = 2},
{.function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_OID},
{.function = HRNPQ_FTYPE, .param = "[1]", .resultInt = HRNPQ_TYPE_INT8},
{.function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = "1259"},
{.function = HRNPQ_GETVALUE, .param = "[0,1]", .resultZ = "-9223372036854775807"},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_RESULT_STR_Z(
hrnPackToStr(pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultRow)),
"1:u32:1259, 2:i64:-9223372036854775807", "row result");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("error when result is not a single column");
#define TEST_QUERY "select * from pg_class limit 1"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.function = HRNPQ_NTUPLES, .resultInt = 1},
{.function = HRNPQ_NFIELDS, .resultInt = 2},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_ERROR(
pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn), DbQueryError,
"expected one column from '" TEST_QUERY "'");
#undef TEST_QUERY
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("single column result");
#define TEST_QUERY "select -2147483647::int4"
#ifndef HARNESS_PQ_REAL
harnessPqScriptSet((HarnessPq [])
{
{.function = HRNPQ_SENDQUERY, .param = "[\"" TEST_QUERY "\"]", .resultInt = 1},
{.function = HRNPQ_CONSUMEINPUT},
{.function = HRNPQ_ISBUSY},
{.function = HRNPQ_GETRESULT},
{.function = HRNPQ_RESULTSTATUS, .resultInt = PGRES_TUPLES_OK},
{.function = HRNPQ_NTUPLES, .resultInt = 1},
{.function = HRNPQ_NFIELDS, .resultInt = 1},
{.function = HRNPQ_FTYPE, .param = "[0]", .resultInt = HRNPQ_TYPE_INT4},
{.function = HRNPQ_GETVALUE, .param = "[0,0]", .resultZ = "-2147483647"},
{.function = HRNPQ_CLEAR},
{.function = HRNPQ_GETRESULT, .resultNull = true},
{.function = NULL}
});
#endif
TEST_RESULT_STR_Z(
hrnPackToStr(pgClientQuery(client, STRDEF(TEST_QUERY), pgClientQueryResultColumn)), "1:i32:-2147483647",
"column result");
#undef TEST_QUERY