pg: Initial support for receiving binary results

Just the initial framework stuff and a few types to test with.
This commit is contained in:
Yorhel 2025-02-07 15:18:45 +01:00
parent 7c8473533d
commit 8f94dd0921
4 changed files with 164 additions and 34 deletions

View file

@ -149,13 +149,6 @@ invalid:
fu_confess("invalid transaction object");
}
/* Read a Perl value from a PGresult.
* Currently assumes text format and just creates a PV. */
static SV *fupg_val(pTHX_ const PGresult *r, int row, int col) {
if (PQgetisnull(r, row, col)) return newSV(0);
return newSVpvn_utf8(PQgetvalue(r, row, col), PQgetlength(r, row, col), 1);
}
typedef struct {
/* Set in $conn->q() */
SV *self;
@ -163,14 +156,19 @@ typedef struct {
UV cookie;
char *query;
SV **bind;
int bindn;
int nbind;
bool text_params;
bool text_results;
/* Set during prepare */
int prepared;
char name[32];
PGresult *describe;
/* Set during execute */
int paramn;
int nparam;
int nfields;
char **param;
const fupg_type **recv;
void **recvctx;
PGresult *result;
} fupg_st;
@ -178,6 +176,7 @@ static SV *fupg_q(pTHX_ fupg_conn *c, const char *query, I32 ax, I32 argc) {
fupg_st *st = safecalloc(1, sizeof(fupg_st));
st->conn = c;
st->cookie = c->cookie;
st->text_params = st->text_results = true; /* TODO: default to false */
SvREFCNT_inc(c->self);
st->query = savepv(query);
@ -185,9 +184,9 @@ static SV *fupg_q(pTHX_ fupg_conn *c, const char *query, I32 ax, I32 argc) {
st->bind = safemalloc((argc-2) * sizeof(SV *));
I32 i;
for (i=2; i < argc; i++) {
st->bind[st->bindn] = newSV(0);
sv_setsv(st->bind[st->bindn], ST(i));
st->bindn++;
st->bind[st->nbind] = newSV(0);
sv_setsv(st->bind[st->nbind], ST(i));
st->nbind++;
}
}
@ -267,12 +266,13 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
if (st->result) fu_confess("Invalid attempt to execute statement multiple times");
/* TODO: prepare can be skipped when prepared statement caching is disabled and (text-format queries or no bind params) */
/* TODO: support binary format params */
fupg_st_prepare(aTHX_ st);
st->param = safemalloc(st->bindn * sizeof(char *));
st->param = safemalloc(st->nbind * sizeof(char *));
int i;
for (i=0; i<st->bindn; i++) {
for (i=0; i<st->nbind; i++) {
st->param[i] = SvPVutf8_nolen(st->bind[i]);
st->paramn++;
st->nparam++;
}
/* I'm not super fond of this approach. Storing the full query results in a
@ -285,7 +285,9 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
* gather that just saves a bit of memory in exchange for more and smaller
* malloc()/free()'s. Performance-wise, it probably won't be much of an
* improvement */
PGresult *r = PQexecPrepared(st->conn->conn, st->name, st->paramn, (const char * const*)st->param, NULL, NULL, 0);
PGresult *r = PQexecPrepared(st->conn->conn,
st->name, st->nparam, (const char * const*)st->param,
NULL, NULL, st->text_results ? 0 : 1);
if (!r) fupg_conn_croak(st->conn , "exec");
switch (PQresultStatus(r)) {
case PGRES_COMMAND_OK:
@ -293,6 +295,24 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
default: fupg_result_croak(r, "exec", st->query);
}
st->result = r;
st->nfields = PQnfields(r);
st->recv = safecalloc(st->nfields, sizeof(*st->recv));
st->recvctx = safecalloc(st->nfields, sizeof(*st->recvctx));
for (i=0; i<st->nfields; i++) {
st->recv[i] = fupg_type_lookup(st->text_results ? 0 : PQftype(r, i));
if (!st->recv[i])
fu_confess("Unable to receive query results of type %u", PQftype(r, i));
}
}
static SV *fupg_st_getval(pTHX_ fupg_st *st, int row, int col) {
PGresult *r = st->result;
if (PQgetisnull(r, row, col)) return newSV(0);
int len = PQgetlength(st->result, row, col);
const fupg_type *t = st->recv[col];
if (t->len && len != t->len) fu_confess("invalid length for type %s: %d\n", t->name, len);
return t->recv(aTHX_ PQgetvalue(r, row, col), len, st->recvctx[col]);
}
static SV *fupg_st_exec(pTHX_ fupg_st *st) {
@ -306,7 +326,7 @@ static SV *fupg_st_val(pTHX_ fupg_st *st) {
if (PQnfields(st->describe) == 0) fu_confess("Invalid use of $st->val() on query returning no data");
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) > 1) fu_confess("Invalid use of $st->val() on query returning more than one row");
SV *sv = PQntuples(st->result) == 0 ? newSV(0) : fupg_val(aTHX_ st->result, 0, 0);
SV *sv = PQntuples(st->result) == 0 ? newSV(0) : fupg_st_getval(aTHX_ st, 0, 0);
return sv_2mortal(sv);
}
@ -316,24 +336,24 @@ static I32 fupg_st_rowl(pTHX_ fupg_st *st, I32 ax) {
if (PQntuples(st->result) == 0) fu_confess("Invalid use of $st->rowl() on query returning zero rows");
if (PQntuples(st->result) > 1) fu_confess("Invalid use of $st->rowl() on query returning more than one row");
if (GIMME_V != G_LIST) {
ST(0) = sv_2mortal(newSViv(PQnfields(st->result)));
ST(0) = sv_2mortal(newSViv(st->nfields));
return 1;
}
int i, nfields = PQnfields(st->result);
(void)POPs;
EXTEND(SP, nfields);
for (i=0; i<nfields; i++) mPUSHs(fupg_val(aTHX_ st->result, 0, i));
return nfields;
EXTEND(SP, st->nfields);
int i;
for (i=0; i<st->nfields; i++) mPUSHs(fupg_st_getval(aTHX_ st, 0, i));
return st->nfields;
}
static SV *fupg_st_rowa(pTHX_ fupg_st *st) {
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) == 0) fu_confess("Invalid use of $st->rowl() on query returning zero rows");
if (PQntuples(st->result) > 1) fu_confess("Invalid use of $st->rowl() on query returning more than one row");
int i, nfields = PQnfields(st->result);
AV *av = newAV_alloc_x(nfields);
AV *av = newAV_alloc_x(st->nfields);
SV *sv = sv_2mortal(newRV_noinc((SV *)av));
for (i=0; i<nfields; i++) av_push_simple(av, fupg_val(aTHX_ st->result, 0, i));
int i;
for (i=0; i<st->nfields; i++) av_push_simple(av, fupg_st_getval(aTHX_ st, 0, i));
return sv;
}
@ -343,12 +363,12 @@ static SV *fupg_st_rowh(pTHX_ fupg_st *st) {
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) == 0) fu_confess("Invalid use of $st->rowh() on query returning zero rows");
if (PQntuples(st->result) > 1) fu_confess("Invalid use of $st->rowh() on query returning more than one row");
int i, nfields = PQnfields(st->result);
HV *hv = newHV();
SV *sv = sv_2mortal(newRV_noinc((SV *)hv));
for (i=0; i<nfields; i++) {
int i;
for (i=0; i<st->nfields; i++) {
const char *key = PQfname(st->result, i);
hv_store(hv, key, -strlen(key), fupg_val(aTHX_ st->result, 0, i), 0);
hv_store(hv, key, -strlen(key), fupg_st_getval(aTHX_ st, 0, i), 0);
}
return sv;
}
@ -358,11 +378,14 @@ static void fupg_st_destroy(fupg_st *st) {
/* Ignore failure, this is just a best-effort attempt to free up resources on the backend */
if (st->prepared) PQclear(PQclosePrepared(st->conn->conn, st->name));
if (st->query) safefree(st->query);
for (i=0; i < st->bindn; i++) SvREFCNT_dec(st->bind[i]);
if (st->bind) safefree(st->bind);
/* XXX: These point into bind SVs (for now): for (i=0; i < st->paramn; i++) safefree(st->param[i]); */
if (st->param) safefree(st->param);
safefree(st->query);
for (i=0; i < st->nbind; i++) SvREFCNT_dec(st->bind[i]);
safefree(st->bind);
/* XXX: These point into bind SVs (for now):
* for (i=0; i < st->nparam; i++) safefree(st->param[i]); */
safefree(st->param);
safefree(st->recv);
safefree(st->recvctx); /* XXX: Needs type-specific free() for the individual pointers */
PQclear(st->describe);
PQclear(st->result);
SvREFCNT_dec(st->conn->self);
@ -372,4 +395,5 @@ static void fupg_st_destroy(fupg_st *st) {
/* TODO: $st->alla, allh, flat, kvv, kva, kvh */
/* TODO: Prepared statement caching */
/* TODO: Binary format fetching & type handling */
/* TODO: Binary format bind parameters */
/* TODO: Custom type handling */