You've already forked pgbackrest
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:
@ -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>
|
||||
|
||||
|
@ -814,8 +814,8 @@ typedef struct BackupStartResult
|
||||
{
|
||||
String *lsn;
|
||||
String *walSegmentName;
|
||||
VariantList *dbList;
|
||||
VariantList *tablespaceList;
|
||||
Pack *dbList;
|
||||
Pack *tablespaceList;
|
||||
} BackupStartResult;
|
||||
|
||||
static BackupStartResult
|
||||
|
141
src/db/db.c
141
src/db/db.c
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
|
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Reference in New Issue
Block a user