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:
parent
7515032261
commit
d95ff76d43
3 changed files with 83 additions and 82 deletions
115
c/pgconn.c
115
c/pgconn.c
|
|
@ -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);
|
||||
|
|
|
|||
41
c/pgtypes.c
41
c/pgtypes.c
|
|
@ -1,34 +1,26 @@
|
|||
typedef struct fupg_send fupg_send;
|
||||
typedef struct fupg_recv fupg_recv;
|
||||
typedef struct fupg_tio fupg_tio;
|
||||
|
||||
/* Send function, takes a Perl value and should write the binary encoded
|
||||
* format into the given fustr. */
|
||||
typedef void (*fupg_send_fn)(pTHX_ const fupg_send *, SV *, fustr *);
|
||||
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_recv *, const char *, int);
|
||||
typedef SV *(*fupg_recv_fn)(pTHX_ const fupg_tio *, const char *, int);
|
||||
|
||||
struct fupg_send {
|
||||
/* Type I/O context */
|
||||
struct fupg_tio {
|
||||
Oid oid;
|
||||
const char *name;
|
||||
fupg_send_fn fn;
|
||||
fupg_send_fn send;
|
||||
fupg_recv_fn recv;
|
||||
union {
|
||||
fupg_send *arrayelem;
|
||||
};
|
||||
};
|
||||
|
||||
struct fupg_recv {
|
||||
Oid oid;
|
||||
const char *name;
|
||||
fupg_recv_fn fn;
|
||||
union {
|
||||
fupg_recv *arrayelem;
|
||||
fupg_tio *arrayelem;
|
||||
};
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Oid oid;
|
||||
Oid elemoid; /* For arrays */
|
||||
Oid elemoid; /* For arrays & domain types */
|
||||
char name[64];
|
||||
fupg_send_fn send;
|
||||
fupg_recv_fn recv;
|
||||
|
|
@ -36,8 +28,8 @@ typedef struct {
|
|||
|
||||
|
||||
|
||||
#define RECVFN(name) static SV *fupg_recv_##name(pTHX_ const fupg_recv *ctx __attribute__((unused)), const char *buf, int len)
|
||||
#define SENDFN(name) static void fupg_send_##name(pTHX_ const fupg_send *ctx __attribute__((unused)), SV *val, fustr *out)
|
||||
#define RECVFN(name) static SV *fupg_recv_##name(pTHX_ const fupg_tio *ctx __attribute__((unused)), const char *buf, int len)
|
||||
#define SENDFN(name) static void fupg_send_##name(pTHX_ const fupg_tio *ctx __attribute__((unused)), SV *val, fustr *out)
|
||||
#define RERR(msg, ...) fu_confess("Error parsing value for type '%s' (oid %u): "msg, ctx->name, ctx->oid __VA_OPT__(,) __VA_ARGS__)
|
||||
#define SERR(msg, ...) fu_confess("Error converting Perl value '%s' to type '%s' (oid %u): "msg, SvPV_nolen(val), ctx->name, ctx->oid __VA_OPT__(,) __VA_ARGS__)
|
||||
#define RLEN(l) if (l != len) RERR("expected %d bytes but got %d", l, len)
|
||||
|
|
@ -62,6 +54,9 @@ typedef struct {
|
|||
} else SERR("expected integer");\
|
||||
if (iv < min || iv > max) SERR("integer out of range")
|
||||
|
||||
/* These are simply marker functions, not supposed to be called directly */
|
||||
RECVFN(domain) { (void)buf; (void)len; RERR("domain type should not be handled by this function"); }
|
||||
SENDFN(domain) { (void)out; SERR("domain type should not be handled by this function"); }
|
||||
|
||||
RECVFN(bool) {
|
||||
RLEN(1);
|
||||
|
|
@ -216,7 +211,7 @@ SENDFN(jsonpath) {
|
|||
|
||||
#define ARRAY_MAXDIM 100
|
||||
|
||||
static SV *fupg_recv_array_elem(pTHX_ const fupg_recv *elem, const char *header, U32 dim, U32 ndim, const char **buf, const char *end) {
|
||||
static SV *fupg_recv_array_elem(pTHX_ const fupg_tio *elem, const char *header, U32 dim, U32 ndim, const char **buf, const char *end) {
|
||||
SV *r = &PL_sv_undef;
|
||||
if (dim == ndim) {
|
||||
if (end - *buf < 4) fu_confess("Invalid array format");
|
||||
|
|
@ -225,7 +220,7 @@ static SV *fupg_recv_array_elem(pTHX_ const fupg_recv *elem, const char *header,
|
|||
|
||||
if (end - *buf < len) fu_confess("Invalid array format");
|
||||
if (len >= 0) {
|
||||
r = elem->fn(aTHX_ elem, *buf, len);
|
||||
r = elem->recv(aTHX_ elem, *buf, len);
|
||||
*buf += len;
|
||||
}
|
||||
|
||||
|
|
@ -257,7 +252,7 @@ RECVFN(array) {
|
|||
return fupg_recv_array_elem(aTHX_ ctx->arrayelem, header, 0, ndim, &data, buf+len);
|
||||
}
|
||||
|
||||
void fupg_send_array_elem(aTHX_ const fupg_send *elem, const U32 *dims, U32 dim, U32 ndim, SV *v, fustr *out, int *hasnull) {
|
||||
void fupg_send_array_elem(aTHX_ const fupg_tio *elem, const U32 *dims, U32 dim, U32 ndim, SV *v, fustr *out, int *hasnull) {
|
||||
SvGETMAGIC(v);
|
||||
if (dim == ndim) {
|
||||
if (!SvOK(v)) {
|
||||
|
|
@ -267,7 +262,7 @@ void fupg_send_array_elem(aTHX_ const fupg_send *elem, const U32 *dims, U32 dim,
|
|||
}
|
||||
size_t lenoff = fustr_len(out);
|
||||
fustr_write(out, "\0\0\0\0", 4);
|
||||
elem->fn(elem, v, out);
|
||||
elem->send(elem, v, out);
|
||||
fu_tobeU(32, fustr_start(out) + lenoff, fustr_len(out) - lenoff - 4);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue