Pg: Add text2bin() and bin2text() conversion methods

This commit is contained in:
Yorhel 2025-04-30 20:02:12 +02:00
parent beeefcf337
commit 76f55f277b
4 changed files with 93 additions and 3 deletions

8
FU.xs
View file

@ -294,6 +294,14 @@ void bin2perl(fupg_conn *c, int oid, SV *sv)
CODE:
ST(0) = fupg_bin2perl(aTHX_ c, oid, sv);
void bin2text(fupg_conn *c, ...)
CODE:
XSRETURN(fupg_bintext(aTHX_ c, 0, ax, items));
void text2bin(fupg_conn *c, ...)
CODE:
XSRETURN(fupg_bintext(aTHX_ c, 1, ax, items));
MODULE = FU PACKAGE = FU::Pg::txn

View file

@ -780,9 +780,24 @@ to be sent to (and further validated by) Postgres. For some types,
C<perl2bin()> may return invalid data on invalid input and C<bin2perl()> may
accept invalid binary data.
=back
=item $conn->bin2text($oid, $bin, ...)
I<TODO:> Methods to convert between bin and text formats.
=item $conn->text2bin($oid, $text, ...)
Convert between the binary format and the PostgreSQL text format. This
conversion requires a round-trip to the server and throws an error if the
connection state is not I<idle> or I<txn_idle>. Since it is Postgres doing the
conversion, the input is properly validated and, in the case of C<bin2text()>,
the result is guaranteed to be suitable for use as a textual bind parameter or
for inclusion in an SQL query (but don't forget to use C<escape_literal()> in
that case).
Calling these methods many times can be pretty slow. If you have several values
to convert, you can do that in a single call to speed things up:
my($text1, $text2, ..) = $conn->bin2text($oid1, $bin1, $oid2, $bin2, ..);
=back
I<TODO:> Methods to query type info.

View file

@ -653,3 +653,53 @@ static SV *fupg_bin2perl(pTHX_ fupg_conn *conn, Oid oid, SV *sv) {
fupg_tio_free(&tio);
return r;
}
static I32 fupg_bintext(pTHX_ fupg_conn *conn, int format, I32 ax, I32 argc) {
int vals = argc/2;
if (argc == 1 || argc % 2 == 0) croak("Usage: $conn->%s(oid, data, ...)", format ? "text2bin" : "bin2text");
if (vals > 1 && GIMME_V != G_LIST) {
ST(0) = sv_2mortal(newSViv(vals));
return 1;
}
Oid *paramtypes = safemalloc(vals * sizeof(*paramtypes));
const char **paramvalues = safemalloc(vals * sizeof(*paramvalues));
int *paramlengths = safemalloc(vals * sizeof(*paramlengths));
int *paramformats = safemalloc(vals * sizeof(*paramformats));
fustr sql;
fustr_init(&sql, NULL, SIZE_MAX);
fustr_write(&sql, "SELECT ", 7);
STRLEN len;
int i;
for (i=0; i<vals; i++) {
paramtypes[i] = SvIV(ST(i*2+1));
paramvalues[i] = format ? SvPVutf8(ST(i*2+2), len) : SvPVbyte(ST(i*2+2), len);
paramlengths[i] = len;
paramformats[i] = format ? 0 : 1;
if (i) fustr_write_ch(&sql, ',');
sql.cur -= 8 - sprintf(fustr_write_buf(&sql, 8), "$%d", i+1);
}
fustr_write_ch(&sql, 0);
PGresult *r = PQexecParams(conn->conn, fustr_start(&sql), vals,
paramtypes, paramvalues, paramlengths, paramformats, format);
safefree(paramtypes);
safefree(paramvalues);
safefree(paramlengths);
safefree(paramformats);
SvREFCNT_dec(sql.sv);
if (!r) fupg_conn_croak(conn, "exec");
if (PQresultStatus(r) != PGRES_TUPLES_OK) fupg_result_croak(r, "exec", sql.sv ? "SELECT $1, ..." : sql.sbuf);
/* The stack is guaranteed to be large enough, since we received 1+2*vals arguments */
for (i=0; i<vals; i++)
ST(i) = newSVpvn_flags(PQgetvalue(r, 0, i), PQgetlength(r, 0, i), SVs_TEMP | (format ? 0 : SVf_UTF8));
PQclear(r);
return vals;
}

View file

@ -42,7 +42,11 @@ sub v($type, $p_in, @args) {
{
my $bin = $conn->perl2bin($oid, $p_in);
ok defined $bin;
is_deeply $conn->bin2perl($oid, $bin), $p_out if $type !~ /\(/;
if ($type !~ /\(/) {
is_deeply $conn->bin2perl($oid, $bin), $p_out;
is $conn->bin2text($oid, $bin), $s_out;
is $conn->text2bin($oid, $s_out), $bin if $type ne 'jsonb'; # jsonb pretty-prints for some reason
}
}
}
sub f($type, $p_in) {
@ -180,6 +184,19 @@ is $conn->q('SELECT ($1::int2[])[2]', [1,2,3,4])->val, 2;
is $conn->q('SELECT ($1::int2vector)[1]', [1,2,3,4])->val, 2;
is $conn->q('SELECT ($1::oidvector)[1]', [1,2,3,4])->val, 2;
is_deeply [$conn->bin2text(
16, $conn->perl2bin(16, 1),
25, 'Hello',
1007, $conn->perl2bin(1007, [-3,1,undef])
)], ['t', 'Hello', '{-3,1,NULL}'];
{
my($b,$s,$a) = $conn->text2bin(16, 't', 25, 'Hello', 1007, '{-3,1,NULL}');
is $conn->bin2perl(16, $b), 1;
is $conn->bin2perl(25, $s), 'Hello';
is_deeply $conn->bin2perl(1007, $a), [-3,1,undef];
}
{
my $v = $conn->q("SELECT '{t,f,NULL}'::bool[]")->val;
is_deeply $v, [true, false, undef];