pg: Add a few result fetching methods

I'm not sure if these are free from memory leaks, need to find a way to
test for that.
This commit is contained in:
Yorhel 2025-02-06 11:36:08 +01:00
parent 711300b227
commit 9d5905e3b4
5 changed files with 223 additions and 21 deletions

View file

@ -47,9 +47,13 @@ typedef enum { PQSHOW_CONTEXT_NEVER, PQSHOW_CONTEXT_ERRORS, PQSHOW_CONTEXT_ALWAY
X(PQfname, char *, const PGresult *, int) \
X(PQfreemem, void, void *) \
X(PQftype, Oid, const PGresult *, int) \
X(PQgetisnull, int, const PGresult *, int, int) \
X(PQgetlength, int, const PGresult *, int, int) \
X(PQgetvalue, char *, const PGresult *, int, int) \
X(PQlibVersion, int, void) \
X(PQnfields, int, const PGresult *) \
X(PQnparams, int, const PGresult *) \
X(PQntuples, int, const PGresult *) \
X(PQparamtype, Oid, const PGresult *, int) \
X(PQprepare, PGresult *, PGconn *, const char *, const char *, int, const Oid *) \
X(PQresStatus, char *, ExecStatusType) \

View file

@ -112,6 +112,13 @@ static SV *fupg_exec(pTHX_ fupg_conn *c, const char *sql) {
return ret;
}
/* 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;
@ -199,6 +206,21 @@ static SV *fupg_st_columns(pTHX_ fupg_st *st) {
return sv_2mortal(newRV_noinc((SV *)av));
}
static void fupg_st_check_dupcols(pTHX_ PGresult *r) {
HV *hv = newHV();
int i, nfields = PQnfields(r);
for (i=0; i<nfields; i++) {
const char *key = PQfname(r, i);
int len = -strlen(key);
if (hv_exists(hv, key, len)) {
SvREFCNT_dec((SV *)hv);
croak("Query returns multiple columns with the same name ('%s')", key);
}
hv_store(hv, key, len, &PL_sv_yes, 0);
}
SvREFCNT_dec((SV *)hv);
}
static void fupg_st_execute(pTHX_ fupg_st *st) {
/* Disallow fetching the results more than once. I don't see a reason why
* someone would need that and disallowing it leaves room for fetching the
@ -240,6 +262,59 @@ static SV *fupg_st_exec(pTHX_ fupg_st *st) {
return fupg_exec_result(st->result);
}
static SV *fupg_st_val(pTHX_ fupg_st *st) {
fupg_st_prepare(aTHX_ st);
if (PQnfields(st->describe) > 1) croak("Invalid use of $st->val() on query returning more than one column");
if (PQnfields(st->describe) == 0) croak("Invalid use of $st->val() on query returning no data");
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) > 1) croak("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);
return sv_2mortal(sv);
}
static I32 fupg_st_rowl(pTHX_ fupg_st *st, I32 ax) {
dSP;
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) == 0) croak("Invalid use of $st->rowl() on query returning zero rows");
if (PQntuples(st->result) > 1) croak("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)));
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;
}
static SV *fupg_st_rowa(pTHX_ fupg_st *st) {
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) == 0) croak("Invalid use of $st->rowl() on query returning zero rows");
if (PQntuples(st->result) > 1) croak("Invalid use of $st->rowl() on query returning more than one row");
int i, nfields = PQnfields(st->result);
AV *av = newAV_alloc_x(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));
return sv;
}
static SV *fupg_st_rowh(pTHX_ fupg_st *st) {
fupg_st_prepare(aTHX_ st);
fupg_st_check_dupcols(aTHX_ st->describe);
fupg_st_execute(aTHX_ st);
if (PQntuples(st->result) == 0) croak("Invalid use of $st->rowh() on query returning zero rows");
if (PQntuples(st->result) > 1) croak("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++) {
const char *key = PQfname(st->result, i);
hv_store(hv, key, -strlen(key), fupg_val(aTHX_ st->result, 0, i), 0);
}
return sv;
}
static void fupg_st_destroy(fupg_st *st) {
int i;
/* Ignore failure, this is just a best-effort attempt to free up resources on the backend */
@ -255,3 +330,9 @@ static void fupg_st_destroy(fupg_st *st) {
SvREFCNT_dec(st->conn->self);
safefree(st);
}
/* TODO: $st->alla, allh, flat, kvv, kva, kvh */
/* TODO: Prepared statement caching */
/* TODO: Transactions */
/* TODO: Binary format fetching & type handling */