pg: Add support for domain types

+ refactor things a bit so that send & recv functions use the same
context struct, because the way they're setup is pretty much the same
for both. This also adds recursive type resolution for bind parameters.
This commit is contained in:
Yorhel 2025-02-10 14:22:38 +01:00
parent 7515032261
commit d95ff76d43
3 changed files with 83 additions and 82 deletions

View file

@ -46,7 +46,8 @@ typedef struct {
const char **param_values; /* Points into conn->buf or st->bind SVs, may be invalid after exec */
int *param_lengths;
int *param_formats;
fupg_recv *recv;
fupg_tio send;
fupg_tio *recv;
PGresult *result;
} fupg_st;
@ -272,7 +273,9 @@ static void fupg_refresh_types(pTHX_ fupg_conn *c) {
const char *sql =
"SELECT oid, typname, typtype"
", CASE WHEN typcategory = 'A' THEN typelem ELSE 0 END"
", CASE WHEN typtype = 'd' THEN typbasetype"
" WHEN typcategory = 'A' THEN typelem"
" ELSE 0 END"
" FROM pg_type"
" ORDER BY oid";
PGresult *r = PQexecParams(c->conn, sql, 0, NULL, NULL, NULL, NULL, 1);
@ -290,15 +293,19 @@ static void fupg_refresh_types(pTHX_ fupg_conn *c) {
t->elemoid = fu_frombeU(32, PQgetvalue(r, i, 3));
if (t->elemoid) {
/* array */
t->send = fupg_send_array;
t->recv = fupg_recv_array;
if (typ == 'd') { /* domain */
t->send = fupg_send_domain;
t->recv = fupg_recv_domain;
} else { /* array */
t->send = fupg_send_array;
t->recv = fupg_recv_array;
}
} else if (typ == 'e') {
/* enum, can use text send/recv */
t->send = fupg_send_text;
t->recv = fupg_recv_text;
} else {
/* TODO: records, domain types, (multi)ranges, custom overrides, by-name lookup for dynamic-oid types */
/* TODO: records, (multi)ranges, custom overrides, by-name lookup for dynamic-oid types */
const fupg_type *builtin = fupg_builtin_byoid(t->oid);
if (builtin) {
t->send = builtin->send;
@ -310,6 +317,7 @@ static void fupg_refresh_types(pTHX_ fupg_conn *c) {
}
static const fupg_type *fupg_lookup_type(pTHX_ fupg_conn *c, int *refresh_done, Oid oid) {
if (oid == 0) return NULL;
const fupg_type *t = NULL;
if (c->types && (t = fupg_type_byoid(c->types, c->ntypes, oid))) return t;
if ((t = fupg_builtin_byoid(oid))) return t;
@ -446,25 +454,40 @@ static void fupg_st_check_dupcols(pTHX_ PGresult *r) {
SvREFCNT_dec((SV *)hv);
}
static void fupg_params_send(pTHX_ fupg_st *st, Oid oid, SV *val, fustr *out, int *refresh_done) {
fupg_send send, elem;
const fupg_type *t = fupg_lookup_type(aTHX_ st->conn, refresh_done, oid);
if (!t) fu_confess("No type found with oid %u", oid);
if (!t->send) fu_confess("Unable to use type '%s' (oid %u) as bind parameter", t->name, t->oid);
send.oid = oid;
send.name = t->name;
send.fn = t->send;
if (t->send == fupg_send_array) {
if (!t->elemoid) fu_confess("Type '%s' (oid %u) is marked as an array type, but element type is unknown", t->name, t->oid);
const fupg_type *e = fupg_lookup_type(aTHX_ st->conn, refresh_done, t->elemoid);
if (!e) fu_confess("No type found with oid %u", t->elemoid);
send.arrayelem = &elem;
elem.oid = e->oid;
elem.name = e->name;
elem.fn = e->send;
assert(e->send != fupg_send_array); /* TODO: might as well fix this, we'll need recursion for record types anyway */
static void fupg_tio_setup(pTHX_ fupg_st *st, fupg_tio *tio, int issend, Oid oid, int *refresh_done) {
tio->oid = oid;
if (st->stflags & (issend ? FUPG_TEXT_PARAMS : FUPG_TEXT_RESULTS)) {
tio->name = "{textfmt}";
tio->send = fupg_send_text;
tio->recv = fupg_recv_text;
return;
}
const fupg_type *e, *t = fupg_lookup_type(aTHX_ st->conn, refresh_done, oid);
if (!t) fu_confess("No type found with oid %u", oid);
if (!t->send || !t->recv) fu_confess("Unable to send or receive type '%s' (oid %u)", t->name, oid);
tio->name = t->name;
if (issend ? t->send == fupg_send_domain : t->recv == fupg_recv_domain) {
e = fupg_lookup_type(aTHX_ st->conn, refresh_done, t->elemoid);
if (!e) fu_confess("Base type %u not found for domain '%s' (oid %u)", t->elemoid, t->name, t->oid);
t = e;
}
tio->send = t->send;
tio->recv = t->recv;
if (issend ? tio->send == fupg_send_array : tio->recv == fupg_recv_array) {
tio->arrayelem = safecalloc(1, sizeof(*tio->arrayelem));
fupg_tio_setup(aTHX_ st, tio->arrayelem, issend, t->elemoid, refresh_done);
}
}
static void fupg_tio_free(fupg_tio *tio) {
if (!tio) return;
if (tio->send == fupg_send_array) {
fupg_tio_free(tio->arrayelem);
safefree(tio->arrayelem);
}
send.fn(aTHX_ &send, val, out);
}
static void fupg_params_setup(pTHX_ fupg_st *st, int *refresh_done) {
@ -486,8 +509,12 @@ static void fupg_params_setup(pTHX_ fupg_st *st, int *refresh_done) {
st->param_values[i] = NULL;
continue;
}
fupg_tio_setup(aTHX_ st, &st->send, 1, PQparamtype(st->describe, i), refresh_done);
off = fustr_len(buf);
fupg_params_send(aTHX_ st, PQparamtype(st->describe, i), st->bind[i], buf, refresh_done);
st->send.send(aTHX_ &st->send, st->bind[i], buf);
fupg_tio_free(&st->send);
memset(&st->send, 0, sizeof(st->send));
st->param_lengths[i] = fustr_len(buf) - off;
st->param_formats[i] = 1;
st->param_values[i] = "";
@ -503,35 +530,6 @@ static void fupg_params_setup(pTHX_ fupg_st *st, int *refresh_done) {
}
}
static void fupg_recv_setup(pTHX_ fupg_st *st, fupg_recv *r, Oid oid, int *refresh_done) {
r->oid = oid;
if (st->stflags & FUPG_TEXT_RESULTS) {
r->name = "{textfmt}";
r->fn = fupg_recv_text;
return;
}
const fupg_type *t = fupg_lookup_type(aTHX_ st->conn, refresh_done, r->oid);
if (!t) fu_confess("No type found with oid %u", r->oid);
if (!t->recv) fu_confess("Unable to receive data of type '%s' (oid %u)", t->name, t->oid);
r->name = t->name;
r->fn = t->recv;
if (r->fn == fupg_recv_array) {
if (!t->elemoid) fu_confess("Type '%s' (oid %u) is marked as an array type, but element type is unknown", t->name, t->oid);
r->arrayelem = safecalloc(1, sizeof(*r->arrayelem));
fupg_recv_setup(aTHX_ st, r->arrayelem, t->elemoid, refresh_done);
}
}
static void fupg_recv_free(fupg_recv *r) {
if (!r) return;
if (r->fn == fupg_recv_array) {
fupg_recv_free(r->arrayelem);
safefree(r->arrayelem);
}
}
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
@ -574,14 +572,14 @@ static void fupg_st_execute(pTHX_ fupg_st *st) {
st->recv = safecalloc(st->nfields, sizeof(*st->recv));
int i;
for (i=0; i<st->nfields; i++)
fupg_recv_setup(aTHX_ st, st->recv + i, PQftype(st->result, i), &refresh_done);
fupg_tio_setup(aTHX_ st, st->recv + i, 0, PQftype(st->result, i), &refresh_done);
}
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);
const fupg_recv *ctx = st->recv+col;
return ctx->fn(aTHX_ ctx, PQgetvalue(r, row, col), PQgetlength(r, row, col));
const fupg_tio *ctx = st->recv+col;
return ctx->recv(aTHX_ ctx, PQgetvalue(r, row, col), PQgetlength(r, row, col));
}
static SV *fupg_st_exec(pTHX_ fupg_st *st) {
@ -653,7 +651,8 @@ static void fupg_st_destroy(fupg_st *st) {
safefree(st->param_values);
safefree(st->param_lengths);
safefree(st->param_formats);
if (st->recv) for (i=0; i<st->nfields; i++) fupg_recv_free(st->recv + i);
if (st->recv) for (i=0; i<st->nfields; i++) fupg_tio_free(st->recv + i);
fupg_tio_free(&st->send);
safefree(st->recv);
PQclear(st->describe);
PQclear(st->result);