在PostgreSQL的libpq库(C API)中,我试图将从文本表示形式返回的bytea字段转换为原始二进制字符串。
例如,对于单个换行符,文本表示为"\\x0a"
(即字符'\\'
,'x'
,'0'
和'a'
,已终止用空字节)。
According to the documentation可以使用PQunescapeBytea()将此文本表示转换回二进制表示。但是,当我使用它时,我只得到一个完全相同的char *
,但删除了前导斜杠"x0a"
。我做错了什么?
char * value = PGgetvalue(res, row, col);
size_t length = 0;
char * bytes = PQunescapeBytea(value, &length);
// inspection of 'bytes' for length shows it's the same, with the '\' removed
答案 0 :(得分:1)
好吧,我正在开发的机器有PostgreSQL 8.4,而服务器有9.1,它正在做bytea的十六进制表示,客户端无法理解。我需要这样做:
SET bytea_output = escape
但是,由于我不想篡改用户设置(甚至可能从我下面改变),我选择提供回退实现并在字符串看起来像新格式时绕过libpq。为简洁起见,删除了命名空间前缀。不是说我正在返回一个Ruby字符串,因此我的代码中有rb_*
和VALUE
内容。
/** Predicate to test of string is of the form \x0afe... */
#define NEW_HEX_P(s, len) (len > 2 && s[0] == '\\' && s[1] == 'x')
/** Lookup table for fast conversion of bytea hex strings to binary data */
static char * HexLookup;
/** Cast from a bytea to a String according to the new (PG 9.0) hex format */
static VALUE cast_bytea_hex(char * hex, size_t len) {
if ((len % 2) != 0) {
rb_raise(rb_eRuntimeError,
"Bad hex value provided for bytea (length not divisible by 2)");
}
size_t buflen = (len - 2) / 2;
char * buffer = malloc(sizeof(char) * buflen);
char * s = hex + 2;
char * b = buffer;
if (buffer == NULL) {
rb_raise(rb_eRuntimeError,
"Failed to allocate %ld bytes for bytea conversion", buflen);
}
for (; *s; s += 2, ++b)
*b = (HexLookup[*s] << 4) + (HexLookup[*(s + 1)]);
VALUE str = rb_str_new(buffer, buflen);
free(buffer);
return str;
}
/** Cast from a bytea to a String according to a regular escape format */
static VALUE cast_bytea_escape(char * escaped, size_t len) {
unsigned char * buffer = PQunescapeBytea(escaped, &len);
if (buffer == NULL) {
rb_raise(rb_eRuntimeError,
"Failed to allocate memory for PQunescapeBytea() conversion");
}
VALUE str = rb_str_new(buffer, len);
PQfreemem(buffer);
return str;
}
/** Get the value as a ruby type */
VALUE cast_value(PGresult * res, int row, int col) {
if (PQgetisnull(res, row, col)) {
return Qnil;
}
char * value = PQgetvalue(res, row, col);
int length = PQgetlength(res, row, col);
switch (PQftype(res, col)) {
case INT2OID:
case INT4OID:
case INT8OID:
return rb_cstr2inum(value, 10);
case BOOLOID:
return (value[0] == 't') ? Qtrue : Qfalse;
case BYTEAOID:
if (NEW_HEX_P(value, length)) {
return cast_bytea_hex(value, length);
} else {
return cast_bytea_escape(value, length);
}
default:
return rb_str_new(value, length);
}
}
/* Initialize hex decoding lookup table. Must be invoked once, before use. */
void Init_casts(void) {
HexLookup = malloc(sizeof(char) * 128);
if (HexLookup == NULL) {
rb_raise(rb_eRuntimeError,
"Failed to allocate 128 bytes for internal lookup table");
}
char c;
for (c = '\0'; c < '\x7f'; ++c)
HexLookup[c] = 0; // Default to NULLs so we don't crash. May be a bad idea.
for (c = '0'; c <= '9'; ++c)
HexLookup[c] = c - '0';
for (c = 'a'; c <= 'f'; ++c)
HexLookup[c] = 10 + c - 'a';
for (c = 'A'; c <= 'F'; ++c)
HexLookup[c] = 10 + c - 'A';
}
所以基本上当我尝试构建一个OID为BYTEAOID
的值时,我首先检查它是否看起来像是新的十六进制格式,如果是,我会对查找表执行解码,否则我正常通过PQunescapeBytea()
。有点糟糕,但这是两个邪恶中较小的一个。
Init_casts();
VALUE rubyval = cast_value(res, 1, 6); // Get the ruby type in row 1, column 6