diff --git a/FU.xs b/FU.xs index 9f9b779..f00f5e5 100644 --- a/FU.xs +++ b/FU.xs @@ -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 diff --git a/FU/Pg.pm b/FU/Pg.pm index d7d4b68..deb5f4d 100644 --- a/FU/Pg.pm +++ b/FU/Pg.pm @@ -619,7 +619,21 @@ supported. C always converts to SQL C. =item bytea The C 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 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 to configure appropriate conversions for these types. =back -I Some handy special types for overriding common conversions. - I Methods to convert between the various formats. I Methods to query type info. diff --git a/c/common.c b/c/common.c index 849d9e8..21d5c7d 100644 --- a/c/common.c +++ b/c/common.c @@ -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) { diff --git a/c/jsonparse.c b/c/jsonparse.c index 383881f..6c742be 100644 --- a/c/jsonparse.c +++ b/c/jsonparse.c @@ -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; diff --git a/c/pgtypes.c b/c/pgtypes.c index e6d586a..481cd03 100644 --- a/c/pgtypes.c +++ b/c/pgtypes.c @@ -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> 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 }, diff --git a/t/pgtypes.t b/t/pgtypes.t index 08b835f..5a17a71 100644 --- a/t/pgtypes.t +++ b/t/pgtypes.t @@ -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}");