fu/c/pgtypes.c
2025-02-08 10:35:43 +01:00

138 lines
4 KiB
C

typedef struct fupg_send fupg_send;
typedef struct fupg_recv fupg_recv;
/* 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 *);
/* Receive function, takes a binary string and should return a Perl value.
* libpq guarantees that the given buffer is aligned to MAXIMUM_ALIGNOF.
*/
typedef SV *(*fupg_recv_fn)(pTHX_ const fupg_recv *, const char *, int);
struct fupg_send {
Oid oid;
const char *name;
fupg_send_fn fn;
};
struct fupg_recv {
Oid oid;
const char *name;
fupg_recv_fn fn;
};
typedef struct {
Oid oid;
char name[16]; /* Postgres has a 64 byte limit on names, but this is sufficient for the core types listed here */
fupg_send_fn send;
fupg_recv_fn recv;
} fupg_core_type;
#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 RLEN(l) if (l != len) fu_confess("Invalid length for type '%s' (oid %u), expected %d but got %d", ctx->name, ctx->oid, l, len)
/* Perl likes to play loose with SV-to-integer conversions, but that's not
* very fun when trying to store values in a database. Text-based bind
* parameters get stricter validation by Postgres, so let's emulate some of
* that for binary parameters as well. */
#define SIV(min, max) IV iv;\
if (SvIOK(val)) iv = SvIV(val); \
else if (SvNOK(val)) { \
NV nv = SvNV(val); \
if (nv < IV_MIN || nv > IV_MAX || fabs(nv - floor(nv)) > 0.0000000001) \
fu_confess("Type '%s' (oid %u) expects an integer but got a floating point", ctx->name, ctx->oid); \
iv = SvIV(val); \
} else if (SvPOK(val)) {\
STRLEN sl; \
UV uv; \
char *s = SvPV(val, sl); \
if (*s == '-' && grok_atoUV(s+1, &uv, NULL) && uv <= ((UV)IV_MAX)+1) iv = SvIV(val);\
else if (grok_atoUV(s, &uv, NULL) && uv <= IV_MAX) iv = SvIV(val);\
else fu_confess("Type '%s' (oid %u) expects an integer", ctx->name, ctx->oid); \
} else fu_confess("Type '%s' (oid %u) expects an integer", ctx->name, ctx->oid);\
if (iv < min || iv > max) fu_confess("Integer %"IVdf" out of range for type '%s' (oid %u)", iv, ctx->name, ctx->oid)
RECVFN(textfmt) {
return newSVpvn_utf8(buf, len, 1);
}
RECVFN(bool) {
RLEN(1);
return *buf ? &PL_sv_yes : &PL_sv_no;
}
SENDFN(bool) {
fustr_write_ch(out, SvTRUE(val) ? 1 : 0);
}
RECVFN(int2) {
RLEN(2);
return newSViv((I16)__builtin_bswap16(*((U16 *)buf)));
}
SENDFN(int2) {
SIV(-32768, 32767);
U16 v = __builtin_bswap16((U16)iv);
fustr_write(out, (const char *)&v, 2);
}
RECVFN(int4) {
RLEN(4);
return newSViv((I32)__builtin_bswap32(*((U32 *)buf)));
}
SENDFN(int4) {
SIV(-2147483648, 2147483647);
U32 v = __builtin_bswap32((U32)iv);
fustr_write(out, (const char *)&v, 4);
}
RECVFN(int8) {
RLEN(8);
return newSViv((I64)__builtin_bswap64(*((U64 *)buf)));
}
SENDFN(int8) {
SIV(IV_MIN, IV_MAX);
U64 v = __builtin_bswap64((U64)SvIV(val));
fustr_write(out, (const char *)&v, 8);
}
#undef RLEN
#undef RECVFN
#undef SENDFN
#define R(name) fupg_recv_##name
#define S(name) fupg_send_##name
/* Sorted by oid to support binary search. */
static const fupg_core_type fupg_core_types[] = {
{ 16, "bool", S(bool), R(bool) },
{ 20, "int8", S(int8), R(int8) },
{ 21, "int2", S(int2), R(int2) },
{ 23, "int4", S(int4), R(int4) },
};
/* TODO: A LOT MORE TYPES */
#undef R
#define FUPG_CORE_TYPES (sizeof(fupg_core_types) / sizeof(fupg_core_type))
static const fupg_core_type *fupg_core_type_byoid(Oid oid) {
int i, b = 0, e = FUPG_CORE_TYPES-1;
while (b <= e) {
i = b + (e - b)/2;
if (fupg_core_types[i].oid == oid) return fupg_core_types+i;
if (fupg_core_types[i].oid < oid) b = i+1;
else e = i-1;
}
return NULL;
}