Postgresql postgres中的bytea存储和检索字节

Postgresql postgres中的bytea存储和检索字节,postgresql,bytea,Postgresql,Bytea,我试图理解如何在postgresql(V8.3)中使用二进制数据。 假设我有一张下表 Table "public.message" Column | Type | Modifiers ---------+---------+----------- id | integer | message | bytea | 我想以以下格式在消息字段中存储数据包: 版本(1字节)、标识符(1字节)、历元(4字节) 我想将此数据打包到消息字段中。假设版本=1,标识符=8,历元=12

我试图理解如何在postgresql(V8.3)中使用二进制数据。 假设我有一张下表

Table "public.message"
Column  |  Type   | Modifiers 
---------+---------+-----------
id      | integer | 
message | bytea   | 
我想以以下格式在消息字段中存储数据包:

版本(1字节)、标识符(1字节)、历元(4字节)

我想将此数据打包到消息字段中。假设版本=1,标识符=8,历元=123456。如何将此数据打包到消息字段中?如何将整数值转换为十六进制。。还是八进制

我还需要将消息取回并解析它。我在看
get_byte
函数,除非有其他方法来解析数据


谢谢

下面是一些示例代码,展示了如何使用服务器端Perl实现这一点。令人烦恼的是,PG认为打包/解包是不受信任的操作,因此这必须由超级用户使用plperlu创建,然后授予非超级用户GRANT EXECUTE访问权限

另一方面,这种语言的选择使处理更复杂的压缩结构变得容易,这与基于SQL get_bytes()/set_bytes()函数的代码相比具有显著的优势。看

1) 第一步:定义表示非压缩记录的SQL复合类型

create type comp as (a smallint, b smallint, c int);
2) 创建一个函数,将记录值打包到bytea中:

create function pack_comp(comp) returns bytea
as $body$
 my $arg=shift;
 my $retval = pack("CCL", $arg->{a},$arg->{b},$arg->{c});
 # encode bytea according to PG doc. For PG>=9.0, use encode_bytea() instead
 $retval =~ s!(\\|[^ -~])!sprintf("\\%03o",ord($1))!ge; # from PG doc
 return $retval;
$body$ language plperlu;
3) 创建一个函数,将bytea解包为复合类型:

create or replace function unpack_comp(bytea) returns comp
as $body$
 my $arg=shift;
 # decode bytea according to PG doc. For PG>=9.0, use decode_bytea() instead
 $arg =~ s!\\(?:\\|(\d{3}))!$1 ? chr(oct($1)) : "\\"!ge;
 my ($v,$i,$e)= unpack("CCL", $arg);
 return {"a"=>$v, "b"=>$i, "c"=>$e};
$body$ language plperlu;
4) 用法:


因此,我能够在
plpg
这是要打包的代码

CREATE FUNCTION pack_numeric_bytes(i_values NUMERIC[], i_byte_sizes NUMERIC[],    i_big_endian BOOLEAN)
RETURNS BYTEA
    DECLARE
        v_bytes BYTEA := NULL;
        v_start INTEGER := 1;
        v_byte BYTEA;
        v_byte_size INTEGER;
        v_value NUMERIC;
        v_binary_value TEXT;
        v_num NUMERIC;
        i INTEGER;
        x INTEGER;
        v_sql TEXT;
    BEGIN
        IF array_upper(i_values, 1) != array_upper(i_byte_sizes, 1) THEN
            RETURN v_bytes;
        END IF;

        FOR x IN array_lower(i_values, 1) .. array_upper(i_values, 1) LOOP

            /* Given value and size at x position */
            v_byte_size := i_byte_sizes[x]::INTEGER;
            v_value := i_values[x];
            /* Convert number to binary form */
            v_sql := $$SELECT $$|| v_value ||$$::bit($$|| v_byte_size*8 ||$$);$$;
            EXECUTE v_sql INTO v_binary_value;
            IF i_big_endian IS TRUE THEN
                /* Convert each byte at a time */
                FOR i IN 1 .. v_byte_size LOOP
                    /* Extract byte from our binary value. 
                    Big endian starts at 1 and then increments of 8 */
                    v_byte := substring(v_binary_value, v_start, 8);
                    /* Convert binary 8 bits to an integer */
                    v_sql := $$SELECT B$$||quote_literal(v_byte)||$$::int8$$;
                    EXECUTE v_sql INTO v_num;
                    /* Build bytea of bytes */
                    v_bytes := COALESCE(v_bytes, '') || set_byte(E' '::BYTEA, 0, v_num::INTEGER);
                    v_start := v_start + 8;

                END LOOP;
            ELSE
                /* Small endian is extracted starting from last byte */
                v_start := (v_byte_size * 8) + 1;
                /* Convert each byte at a time */
                FOR i IN 1 .. v_byte_size LOOP
                    v_start := v_start - 8;
                    v_byte := substring(v_binary_value, v_start, 8);
                    /* Convert binary 8 bits to an integer */
                    v_sql := $$SELECT B$$||quote_literal(v_byte)||$$::int8$$;
                    EXECUTE v_sql INTO v_num;
                    /* Build bytea of bytes */
                    v_bytes := COALESCE(v_bytes, '') || set_byte(E' '::BYTEA, 0, v_num::INTEGER);
                END LOOP;

            END IF; /* END endian check */

            v_start := 1;

        END LOOP;
        RETURN v_bytes;
    END;
下面是要解包的代码:

CREATE OR REPLACE FUNCTION public.unpack_numeric_bytes(i_bytes bytea, i_byte_sizes INTEGER[], i_big_endian BOOLEAN)
RETURNS NUMERIC[]
SECURITY DEFINER AS
    DECLARE
        v_bytes BYTEA;
        v_start INTEGER := 1;
        v_byte_index INTEGER := 0;
        v_bit_shift INTEGER := 0;

        v_length INTEGER;
        v_size INTEGER;
        v_sum_byte_sizes INTEGER;

        v_vals NUMERIC[] := '{}';
        v_val BIGINT := 0;

        i INTEGER;
        x INTEGER;
        v_sql TEXT;
    BEGIN
       v_sql := $$SELECT $$|| array_to_string(i_byte_sizes, '+')||$$;$$;

        EXECUTE v_sql INTO v_sum_byte_sizes;

        IF length(i_bytes) != v_sum_byte_sizes::INTEGER THEN
            RETURN v_vals;
        END IF;

        /* Loop through values of bytea (split by their sizes) */
        FOR x IN array_lower(i_byte_sizes, 1) .. array_upper(i_byte_sizes, 1) LOOP

            v_size := i_byte_sizes[x];
            v_bytes := substring(i_bytes, v_start, v_size);
            v_length := length(v_bytes);

            IF i_big_endian IS TRUE THEN

                v_byte_index := v_length - 1;

                FOR i IN 1..v_length LOOP
                    v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
                    v_bit_shift := v_bit_shift + 8;
                    v_byte_index := v_byte_index - 1;
                END LOOP;
            ELSE

                FOR i IN 1..v_length LOOP
                    v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
                    v_bit_shift := v_bit_shift + 8;
                    v_byte_index := v_byte_index + 1;
                END LOOP;

            END IF;

            v_vals := array_append(v_vals, v_val::NUMERIC);
            /* Calculate next value start index */
            v_start := v_start + v_size;
            v_byte_index := 0;
            v_bit_shift := 0;
            v_val := 0;

        END LOOP;

        RETURN v_vals;
    END;
创建或替换函数public.unpack_numeric_bytea,i_byte_size INTEGER[],i_big_endian BOOLEAN)
返回数值[]
安全定义器
声明
v_BYTEA;
v_开始整数:=1;
v_字节索引整数:=0;
v_位移位整数:=0;
v_长度整数;
v_大小整数;
v_总和_字节_大小为整数;
v_vals NUMERIC[]:='{}';
v_val BIGINT:=0;
i整数;
x整数;
v_sql文本;
开始
v_sql:=$$选择$$| |数组|到_字符串(i_字节_大小,+')|$$;
将v_sql执行为v_sum_byte_大小;
如果长度(i_字节)!=v_sum_byte_size::INTEGER THEN
返回v_vals;
如果结束;
/*循环遍历bytea的值(按大小划分)*/
对于数组中较低的x(i字节大小,1)。。数组\u上限(i\u字节\u大小,1)循环
v_size:=i_字节大小[x];
v_字节:=子字符串(i_字节、v_开始、v_大小);
v_长度:=长度(v_字节);
如果我是真的,那么
v_字节索引:=v_长度-1;
对于1..v_长度循环中的i

v_val:=v_val+(如果可以使用plperl,则获取_字节(v_字节,v_字节索引),然后解包()在PG SQL函数中没有等价物。或者考虑做客户端。有什么特殊的原因为什么要将这些值打包到ByTeA列中?为什么不只是三个数字列?如果你真的想这样做,你最好用Python或Perl这样的外部语言来构建一个函数。这是他的回答。原因是我正在处理的现有架构的消息字段为bytea,这就是我必须使用的。pg中没有对它的支持,这让我感到非常惊讶。我知道python中的pack函数就是一个例子。感谢您花时间写这篇文章。令人印象深刻!我只是不确定这会不会我为我工作。我也在试图理解整个二进制打包是如何工作的。在你的例子中,
40420f00
是1000000,但是当我使用在线计算器时,它显示“f4240”…这与某种字节重新排列有关吗?是的,这是字节的顺序,它是“本机的”x86机器上的一个。可以使用pack的“CCL>”模板而不是“CCL”获取Big-endian,谢谢!你能解释一下
v|u sql:=$$SELECT B$$$| quote|u literal(v|u byte)|$$::int8$$$$
?因为这一行,我遇到了一个让它工作的问题
CREATE OR REPLACE FUNCTION public.unpack_numeric_bytes(i_bytes bytea, i_byte_sizes INTEGER[], i_big_endian BOOLEAN)
RETURNS NUMERIC[]
SECURITY DEFINER AS
    DECLARE
        v_bytes BYTEA;
        v_start INTEGER := 1;
        v_byte_index INTEGER := 0;
        v_bit_shift INTEGER := 0;

        v_length INTEGER;
        v_size INTEGER;
        v_sum_byte_sizes INTEGER;

        v_vals NUMERIC[] := '{}';
        v_val BIGINT := 0;

        i INTEGER;
        x INTEGER;
        v_sql TEXT;
    BEGIN
       v_sql := $$SELECT $$|| array_to_string(i_byte_sizes, '+')||$$;$$;

        EXECUTE v_sql INTO v_sum_byte_sizes;

        IF length(i_bytes) != v_sum_byte_sizes::INTEGER THEN
            RETURN v_vals;
        END IF;

        /* Loop through values of bytea (split by their sizes) */
        FOR x IN array_lower(i_byte_sizes, 1) .. array_upper(i_byte_sizes, 1) LOOP

            v_size := i_byte_sizes[x];
            v_bytes := substring(i_bytes, v_start, v_size);
            v_length := length(v_bytes);

            IF i_big_endian IS TRUE THEN

                v_byte_index := v_length - 1;

                FOR i IN 1..v_length LOOP
                    v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
                    v_bit_shift := v_bit_shift + 8;
                    v_byte_index := v_byte_index - 1;
                END LOOP;
            ELSE

                FOR i IN 1..v_length LOOP
                    v_val := v_val + (get_byte(v_bytes, v_byte_index) << v_bit_shift);
                    v_bit_shift := v_bit_shift + 8;
                    v_byte_index := v_byte_index + 1;
                END LOOP;

            END IF;

            v_vals := array_append(v_vals, v_val::NUMERIC);
            /* Calculate next value start index */
            v_start := v_start + v_size;
            v_byte_index := 0;
            v_bit_shift := 0;
            v_val := 0;

        END LOOP;

        RETURN v_vals;
    END;