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; }