pg: Add support for record/composite types

This is simply magical. \o/

Vendored in khashl.h. I wouldn't have used it if this were the only
place where I'd need a custom hash table, but it should come in handy
for other tasks as well, especially when I get to implementing an LRU
for prepared statement caching.

(Can all be done with Perl HV's, but they're less efficient and more
cumbersome for these tasks)
This commit is contained in:
Yorhel 2025-02-11 15:56:35 +01:00
parent 33fe0d98a8
commit 87d99e412b
5 changed files with 642 additions and 4 deletions

View file

@ -7,6 +7,15 @@ typedef void (*fupg_send_fn)(pTHX_ const fupg_tio *, SV *, fustr *);
/* Receive function, takes a binary string and should return a Perl value. */
typedef SV *(*fupg_recv_fn)(pTHX_ const fupg_tio *, const char *, int);
/* Record/composite type definition */
typedef struct {
int nattrs;
struct {
Oid oid;
char name[64];
} attrs[];
} fupg_record;
/* Type I/O context */
struct fupg_tio {
Oid oid;
@ -15,12 +24,16 @@ struct fupg_tio {
fupg_recv_fn recv;
union {
fupg_tio *arrayelem;
struct {
const fupg_record *info;
fupg_tio *tio;
} record;
};
};
typedef struct {
Oid oid;
Oid elemoid; /* For arrays & domain types */
Oid elemoid; /* For arrays & domain types; relid for records */
char name[64];
fupg_send_fn send;
fupg_recv_fn recv;
@ -332,6 +345,62 @@ SENDFN(array) {
#undef ARRAY_MAXDIM
RECVFN(record) {
if (len < 4) RERR("input data too short");
I32 nfields = fu_frombeI(32, buf);
if (nfields != ctx->record.info->nattrs) RERR("expected %d fields but got %d", ctx->record.info->nattrs, nfields);
buf += 4; len -= 4;
HV *hv = newHV();
SV *sv = sv_2mortal(newRV_noinc((SV *)hv));
I32 i;
for (i=0; i<nfields; i++) {
if (len < 8) RERR("input data too short");
U32 oid = fu_frombeU(32, buf);
if (oid != ctx->record.info->attrs[i].oid)
RERR("expected field %d to be of type %u but got %u", i, ctx->record.info->attrs[i].oid, oid);
I32 vlen = fu_frombeI(32, buf+4);
SV *r = &PL_sv_undef;
buf += 8; len -= 8;
if (vlen > len) RERR("input data too short");
if (vlen >= 0) {
r = ctx->record.tio[i].recv(aTHX_ ctx->record.tio+i, buf, vlen);
buf += vlen; len -= vlen;
}
hv_store(hv, ctx->record.info->attrs[i].name, -strlen(ctx->record.info->attrs[i].name), r, 0);
}
return SvREFCNT_inc(sv);
}
SENDFN(record) {
if (!SvROK(val)) SERR("expected a hashref");
SV *sv = SvRV(val);
SvGETMAGIC(sv);
if (SvTYPE(sv) != SVt_PVHV) SERR("expected a hashref");
HV *hv = (HV *)sv;
fustr_writebeU(32, out, ctx->record.info->nattrs);
I32 i;
for (i=0; i<ctx->record.info->nattrs; i++) {
fustr_writebeI(32, out, ctx->record.info->attrs[i].oid);
SV **rsv = hv_fetch(hv, ctx->record.info->attrs[i].name, -strlen(ctx->record.info->attrs[i].name), 0);
if (!rsv || !*rsv) {
fustr_writebeI(32, out, -1);
continue;
}
sv = *rsv;
SvGETMAGIC(sv);
if (!SvOK(sv)) {
fustr_writebeI(32, out, -1);
continue;
}
size_t lenoff = fustr_len(out);
fustr_write(out, "\0\0\0\0", 4);
ctx->record.tio[i].send(ctx->record.tio+i, sv, out);
fu_tobeU(32, fustr_start(out) + lenoff, fustr_len(out) - lenoff - 4);
}
}
RECVFN(inet) { /* Also works for cidr */
char tmp[128];
if (len < 8) RERR("input data too short");