Pg: Add "$hex" pseudo-type

This commit is contained in:
Yorhel 2025-02-28 13:41:48 +01:00
parent baf0f90bd5
commit 15954f4ad5
6 changed files with 63 additions and 15 deletions

1
FU.xs
View file

@ -236,6 +236,7 @@ void q(fupg_conn *c, SV *sv, ...)
void _set_type(fupg_conn *c, SV *name, SV *sendsv, SV *recvsv)
CODE:
fupg_set_type(c, name, sendsv, recvsv);
XSRETURN(1);
MODULE = FU PACKAGE = FU::Pg::txn

View file

@ -619,7 +619,21 @@ supported. C<undef> always converts to SQL C<NULL>.
=item bytea
The C<bytea> type represents arbitrary binary data and this module will pass
that along as raw binary strings.
that along as raw binary strings. If you prefer to work with hex strings
instead, use:
$conn->set_type(bytea => '$hex');
The I<bytea> and the I<$hex> (pseudo-)types can be applied to any other type to
convert between the PostgreSQL binary wire format and Perl strings. For
example, if you prefer to receive integers as big-endian hex strings, you can
do that:
$conn->set_type(int4 => recv => '$hex');
Or to treat UUIDs as 16-byte strings:
$conn->set_type(uuid => 'bytea');
=item timestamp / timestamptz
@ -724,8 +738,6 @@ C<set_type()> to configure appropriate conversions for these types.
=back
I<TODO:> Some handy special types for overriding common conversions.
I<TODO:> Methods to convert between the various formats.
I<TODO:> Methods to query type info.

View file

@ -178,6 +178,11 @@ static double fu_timediff(const struct timespec *a, const struct timespec *b) {
}
static int fu_hexdig(char x) {
return x >= '0' && x <= '9' ? x-'0' : x >= 'A' && x <= 'F' ? x-'A'+10 : x >= 'a' && x <= 'f' ? x-'a'+10 : 0x10000;
}
/* -1 if arg is not a bool, 0 on false, 1 on true */
static int fu_2bool(pTHX_ SV *val) {

View file

@ -30,26 +30,22 @@ static inline int fujson_parse_string_escape(pTHX_ fujson_parse_ctx *ctx, fustr
case 'r': *(r->cur++) = 0x0d; break;
case 'u':
/* (awful code adapted from ncdu) */
#define INV (1<<16)
#define hn(n) (n >= '0' && n <= '9' ? n-'0' : n >= 'A' && n <= 'F' ? n-'A'+10 : n >= 'a' && n <= 'f' ? n-'a'+10 : INV)
#define h4(b) (hn((b)[0])<<12) + (hn((b)[1])<<8) + (hn((b)[2])<<4) + hn((b)[3])
#define h4(b) (fu_hexdig((b)[0])<<12) + (fu_hexdig((b)[1])<<8) + (fu_hexdig((b)[2])<<4) + fu_hexdig((b)[3])
if (ctx->end - ctx->buf < 4) return 1;
n = h4(ctx->buf);
if (n >= INV || (n & 0xfc00) == 0xdc00) return 1;
if (n >= 0x10000 || (n & 0xfc00) == 0xdc00) return 1;
ctx->buf += 4;
if ((n & 0xfc00) == 0xd800) { /* high surrogate */
if (ctx->end - ctx->buf < 6) return 1;
if (ctx->buf[0] != '\\' || ctx->buf[1] != 'u') return 1;
s = h4(ctx->buf+2);
if (s >= INV || (s & 0xfc00) != 0xdc00) return 1;
if (s >= 0x10000 || (s & 0xfc00) != 0xdc00) return 1;
n = 0x10000 + (((n & 0x03ff) << 10) | (s & 0x03ff));
ctx->buf += 6;
}
r->cur = (char *)uvchr_to_utf8((U8 *)r->cur, n);
if (n >= 0x80) r->setutf8 = 1;
break;
#undef INV
#undef hn
#undef h4
default:
return 1;

View file

@ -156,6 +156,33 @@ SENDFN(bytea) {
fustr_write(out, buf, len);
}
RECVFN(hex) {
SV *r = newSV(len ? len * 2 : 1);
SvPOK_only(r);
char *out = SvPVX(r);
const unsigned char *in = (const unsigned char *)buf;
int i;
for (i=0; i<len; i++) {
*out++ = PL_hexdigit[(in[i] >> 4) & 0x0f];
*out++ = PL_hexdigit[in[i] & 0x0f];
}
SvCUR_set(r, len * 2);
return r;
}
SENDFN(hex) {
STRLEN len;
const char *in = SvPV(val, len);
const char *end = in + len;
if (len % 2) SERR("Invalid hex string");
while (in < end) {
int v = (fu_hexdig(*in)<<4) + fu_hexdig(in[1]);
if (v > 0xff) SERR("Invalid hex string");
fustr_write_ch(out, v);
in += 2;
}
}
RECVFN(char) {
RLEN(1);
return newSVpvn(buf, len);
@ -515,7 +542,7 @@ RECVFN(uuid) {
RLEN(16);
char tmp[64];
char *out = tmp;
unsigned char *in = (unsigned char *)buf;
const unsigned char *in = (const unsigned char *)buf;
int i;
for (i=0; i<16; i++) {
if (i == 4 || i == 6 || i == 8 || i == 10) *out++ = '-';
@ -534,9 +561,8 @@ SENDFN(uuid) {
for (; *in; in++) {
if (*in == '}') break;
if (dig == 0x10 && *in == '-') continue;
unsigned char x = *in;
x = x >= '0' && x <= '9' ? x-'0' : x >= 'A' && x <= 'F' ? x-'A'+10 : x >= 'a' && x <= 'f' ? x-'a'+10 : 0x10;
if (x == 0x10) SERR("invalid UUID");
int x = fu_hexdig(*in);
if (x > 0x10) SERR("invalid UUID");
if (bytes >= 16) SERR("invalid UUID");
if (dig == 0x10) dig = x;
else {
@ -794,7 +820,8 @@ static const fupg_type fupg_builtin[] = {
/* List of special types for use with set_type() */
#define SPECIALS\
T("$date_str", date_str)
T("$date_str", date_str)\
T("$hex", hex )
static const fupg_type fupg_specials[] = {
#define T(name, fun) { 0, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun },

View file

@ -67,6 +67,13 @@ v bytea => 'hello', undef, '\x68656c6c6f';
v bytea => "\xaf\x90", undef, '\xaf90';
f bytea => "\x{1234}";
$conn->set_type(bytea => '$hex');
v bytea => '', undef, '\x';
v bytea => '68656c6c6f', undef, '\x68656c6c6f';
v bytea => "af90", undef, '\xaf90';
f bytea => 'a';
f bytea => 'a f';
v '"char"' => $_ for (1, '1', 'a', 'A', '-');
v '"char"' => "\x84", undef, '\204';
f '"char"' => $_ for ('', 'ab', "\x{1234}");