1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-05 00:28:52 +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

@ -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,70 +256,90 @@ 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)
{
int rowTotal = PQntuples(pgResult);
int columnTotal = PQnfields(pgResult);
if (rowTotal != 1)
THROW_FMT(DbQueryError, "expected one row from '%s'", strZ(query));
// Get column types
Oid *columnType = memNew(sizeof(int) * (size_t)columnTotal);
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);
for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++)
columnType[columnIdx] = PQftype(pgResult, columnIdx);
// Get values
for (int rowIdx = 0; rowIdx < rowTotal; rowIdx++)
{
if (resultType == pgClientQueryResultAny)
pckWriteArrayBeginP(pack);
for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++)
columnType[columnIdx] = PQftype(pgResult, columnIdx);
// Get values
for (int rowIdx = 0; rowIdx < rowTotal; rowIdx++)
{
VariantList *resultRow = varLstNew();
char *value = PQgetvalue(pgResult, rowIdx, columnIdx);
for (int columnIdx = 0; columnIdx < columnTotal; columnIdx++)
// If value is zero-length then check if it is null
if (value[0] == '\0' && PQgetisnull(pgResult, rowIdx, columnIdx))
{
char *value = PQgetvalue(pgResult, rowIdx, columnIdx);
// If value is zero-length then check if it is null
if (value[0] == '\0' && PQgetisnull(pgResult, rowIdx, columnIdx))
pckWriteNullP(pack);
}
// Else convert the value to a variant
else
{
// Convert column type. Not all PostgreSQL types are supported but these should suffice.
switch (columnType[columnIdx])
{
varLstAdd(resultRow, NULL);
}
// Else convert the value to a variant
else
{
// Convert column type. Not all PostgreSQL types are supported but these should suffice.
switch (columnType[columnIdx])
{
// Boolean type
case 16: // bool
varLstAdd(resultRow, varNewBool(varBoolForce(varNewStrZ(value))));
break;
// Boolean type
case 16: // bool
pckWriteBoolP(pack, varBoolForce(varNewStrZ(value)), .defaultWrite = true);
break;
// Text/char types
case 18: // char
case 19: // name
case 25: // text
varLstAdd(resultRow, varNewStrZ(value));
break;
// Text/char types
case 18: // char
case 19: // name
case 25: // text
pckWriteStrP(pack, STR(value), .defaultWrite = true);
break;
// Integer types
case 20: // int8
case 23: // int4
case 26: // oid
varLstAdd(resultRow, varNewInt64(cvtZToInt64(value)));
break;
// 64-bit integer type
case 20: // int8
pckWriteI64P(pack, cvtZToInt64(value), .defaultWrite = true);
break;
default:
THROW_FMT(
FormatError, "unable to parse type %u in column %d for query '%s'",
columnType[columnIdx], columnIdx, strZ(query));
}
// 32-bit integer type
case 23: // int4
pckWriteI32P(pack, cvtZToInt(value), .defaultWrite = true);
break;
// 32-bit unsigned integer type
case 26: // oid
pckWriteU32P(pack, cvtZToUInt(value), .defaultWrite = true);
break;
default:
THROW_FMT(
FormatError, "unable to parse type %u in column %d for query '%s'",
columnType[columnIdx], columnIdx, strZ(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);