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) void _set_type(fupg_conn *c, SV *name, SV *sendsv, SV *recvsv)
CODE: CODE:
fupg_set_type(c, name, sendsv, recvsv); fupg_set_type(c, name, sendsv, recvsv);
XSRETURN(1);
MODULE = FU PACKAGE = FU::Pg::txn MODULE = FU PACKAGE = FU::Pg::txn

View file

@ -619,7 +619,21 @@ supported. C<undef> always converts to SQL C<NULL>.
=item bytea =item bytea
The C<bytea> type represents arbitrary binary data and this module will pass 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 =item timestamp / timestamptz
@ -724,8 +738,6 @@ C<set_type()> to configure appropriate conversions for these types.
=back =back
I<TODO:> Some handy special types for overriding common conversions.
I<TODO:> Methods to convert between the various formats. I<TODO:> Methods to convert between the various formats.
I<TODO:> Methods to query type info. 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 */ /* -1 if arg is not a bool, 0 on false, 1 on true */
static int fu_2bool(pTHX_ SV *val) { 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 'r': *(r->cur++) = 0x0d; break;
case 'u': case 'u':
/* (awful code adapted from ncdu) */ /* (awful code adapted from ncdu) */
#define INV (1<<16) #define h4(b) (fu_hexdig((b)[0])<<12) + (fu_hexdig((b)[1])<<8) + (fu_hexdig((b)[2])<<4) + fu_hexdig((b)[3])
#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])
if (ctx->end - ctx->buf < 4) return 1; if (ctx->end - ctx->buf < 4) return 1;
n = h4(ctx->buf); n = h4(ctx->buf);
if (n >= INV || (n & 0xfc00) == 0xdc00) return 1; if (n >= 0x10000 || (n & 0xfc00) == 0xdc00) return 1;
ctx->buf += 4; ctx->buf += 4;
if ((n & 0xfc00) == 0xd800) { /* high surrogate */ if ((n & 0xfc00) == 0xd800) { /* high surrogate */
if (ctx->end - ctx->buf < 6) return 1; if (ctx->end - ctx->buf < 6) return 1;
if (ctx->buf[0] != '\\' || ctx->buf[1] != 'u') return 1; if (ctx->buf[0] != '\\' || ctx->buf[1] != 'u') return 1;
s = h4(ctx->buf+2); 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)); n = 0x10000 + (((n & 0x03ff) << 10) | (s & 0x03ff));
ctx->buf += 6; ctx->buf += 6;
} }
r->cur = (char *)uvchr_to_utf8((U8 *)r->cur, n); r->cur = (char *)uvchr_to_utf8((U8 *)r->cur, n);
if (n >= 0x80) r->setutf8 = 1; if (n >= 0x80) r->setutf8 = 1;
break; break;
#undef INV
#undef hn
#undef h4 #undef h4
default: default:
return 1; return 1;

View file

@ -156,6 +156,33 @@ SENDFN(bytea) {
fustr_write(out, buf, len); 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) { RECVFN(char) {
RLEN(1); RLEN(1);
return newSVpvn(buf, len); return newSVpvn(buf, len);
@ -515,7 +542,7 @@ RECVFN(uuid) {
RLEN(16); RLEN(16);
char tmp[64]; char tmp[64];
char *out = tmp; char *out = tmp;
unsigned char *in = (unsigned char *)buf; const unsigned char *in = (const unsigned char *)buf;
int i; int i;
for (i=0; i<16; i++) { for (i=0; i<16; i++) {
if (i == 4 || i == 6 || i == 8 || i == 10) *out++ = '-'; if (i == 4 || i == 6 || i == 8 || i == 10) *out++ = '-';
@ -534,9 +561,8 @@ SENDFN(uuid) {
for (; *in; in++) { for (; *in; in++) {
if (*in == '}') break; if (*in == '}') break;
if (dig == 0x10 && *in == '-') continue; if (dig == 0x10 && *in == '-') continue;
unsigned char x = *in; int x = fu_hexdig(*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");
if (x == 0x10) SERR("invalid UUID");
if (bytes >= 16) SERR("invalid UUID"); if (bytes >= 16) SERR("invalid UUID");
if (dig == 0x10) dig = x; if (dig == 0x10) dig = x;
else { else {
@ -794,7 +820,8 @@ static const fupg_type fupg_builtin[] = {
/* List of special types for use with set_type() */ /* List of special types for use with set_type() */
#define SPECIALS\ #define SPECIALS\
T("$date_str", date_str) T("$date_str", date_str)\
T("$hex", hex )
static const fupg_type fupg_specials[] = { static const fupg_type fupg_specials[] = {
#define T(name, fun) { 0, 0, {name"\0"}, fupg_send_##fun, fupg_recv_##fun }, #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'; v bytea => "\xaf\x90", undef, '\xaf90';
f bytea => "\x{1234}"; 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"' => $_ for (1, '1', 'a', 'A', '-');
v '"char"' => "\x84", undef, '\204'; v '"char"' => "\x84", undef, '\204';
f '"char"' => $_ for ('', 'ab', "\x{1234}"); f '"char"' => $_ for ('', 'ab', "\x{1234}");