-- -- bytea conversion. A bytea is a raw byte string, so it reaches Ruby as a -- binary (ASCII-8BIT) String holding its exact bytes -- NUL-safe -- rather than -- as its hex text. Returning a Ruby String into a bytea takes the string's -- raw bytes verbatim (any encoding), with no hex/escape parsing. -- SET bytea_output = 'hex'; -- A bytea argument is a binary String; its bytes match the input exactly. CREATE FUNCTION b_form(bytea) RETURNS text LANGUAGE plruby AS $$ "#{args[0].class}:#{args[0].encoding}:#{args[0].bytesize}" $$; SELECT b_form('\xdeadbeef'::bytea); b_form --------------------- String:ASCII-8BIT:4 (1 row) SELECT b_form('\x00ff01fe'::bytea); -- NUL and high bytes preserved b_form --------------------- String:ASCII-8BIT:4 (1 row) SELECT b_form('\x'::bytea); -- empty b_form --------------------- String:ASCII-8BIT:0 (1 row) -- The bytes are the raw content, so encode() of the echoed value round-trips. CREATE FUNCTION b_echo(bytea) RETURNS bytea LANGUAGE plruby AS $$ args[0] $$; SELECT encode(b_echo('\x00ff01fe'::bytea), 'hex'); encode ---------- 00ff01fe (1 row) -- The function can inspect and transform the raw bytes. CREATE FUNCTION b_first_byte(bytea) RETURNS int LANGUAGE plruby AS $$ args[0].bytes[0] $$; SELECT b_first_byte('\x41ff00'::bytea); -- 65 b_first_byte -------------- 65 (1 row) -- Building a bytea from raw Ruby bytes. CREATE FUNCTION b_make() RETURNS bytea LANGUAGE plruby AS $$ [0, 255, 16, 32].pack('C*') $$; SELECT encode(b_make(), 'hex'); encode ---------- 00ff1020 (1 row) -- A Ruby String with embedded NULs becomes those exact bytes. CREATE FUNCTION b_nul() RETURNS bytea LANGUAGE plruby AS $$ "a\x00b".b $$; SELECT encode(b_nul(), 'hex'); encode -------- 610062 (1 row) -- The empty bytea. CREATE FUNCTION b_empty() RETURNS bytea LANGUAGE plruby AS $$ "".b $$; SELECT encode(b_empty(), 'hex'); encode -------- (1 row) -- bytea inside a composite: the field is a binary String too. CREATE TYPE b_rec AS (tag text, blob bytea); CREATE FUNCTION b_rec_in(b_rec) RETURNS int LANGUAGE plruby AS $$ args[0]['blob'].bytesize $$; SELECT b_rec_in(ROW('x', '\x00ff01fe')::b_rec); -- 4 b_rec_in ---------- 4 (1 row) CREATE FUNCTION b_rec_out() RETURNS b_rec LANGUAGE plruby AS $$ {'tag' => 'y', 'blob' => [1, 2, 3].pack('C*')} $$; SELECT (b_rec_out()).tag, encode((b_rec_out()).blob, 'hex'); tag | encode -----+-------- y | 010203 (1 row) -- bytea read back through SPI is also a binary String. CREATE FUNCTION b_via_spi() RETURNS int LANGUAGE plruby AS $$ r = spi_exec("SELECT '\\x00ff01fe'::bytea AS b") spi_fetch_row(r)['b'].bytesize $$; SELECT b_via_spi(); -- 4 b_via_spi ----------- 4 (1 row) -- bytea[] arguments arrive as an Array of binary Strings, and a returned -- Array of binary Strings becomes a bytea[]. CREATE FUNCTION b_arr_in(bytea[]) RETURNS int LANGUAGE plruby AS $$ args[0].map(&:bytesize).inject(0, :+) $$; SELECT b_arr_in(ARRAY['\x0000'::bytea, '\xffffff'::bytea]); -- 5 b_arr_in ---------- 5 (1 row) CREATE FUNCTION b_arr_out() RETURNS bytea[] LANGUAGE plruby AS $$ [[0, 255].pack('C*'), [16].pack('C*')] $$; SELECT encode((b_arr_out())[1], 'hex'), encode((b_arr_out())[2], 'hex'); encode | encode --------+-------- 00ff | 10 (1 row)