2

PostgreSQL の libpq ライブラリ (C API) で、テキスト表現で返される bytea フィールドから生のバイナリ文字列に変換しようとしています。

たとえば、改行文字が 1 つの場合、テキスト表現は次のようになります"\\x0a"(つまり、文字'\\''x''0'および'a'がヌル バイトで終了します)。

ドキュメントによると、このテキスト表現は 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
4

1 に答える 1

1

さて、私が開発しているマシンには PostgreSQL 8.4 が搭載されていますが、サーバーには 9.1 が搭載されており、クライアントが理解できないバイトの 16 進表現を行っています。私はする必要があります:

SET bytea_output = escape

ただし、ユーザー設定を改ざんしたくないので (自分で変更することさえあります)、フォールバック実装を提供し、文字列が新しい形式のように見える場合は libpq をバイパスすることにしました。簡潔にするために名前空間の接頭辞を削除しました。Ruby String を返しているわけではないため、コードにrb_*andを使用しています。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、最初に新しい 16 進形式のように見えるかどうかを確認し、そうであれば、ルックアップ テーブルに対してデコードを実行します。そうでなければPQunescapeBytea()、通常どおりに進みます。ちょっとひどいですが、それは2つの悪の小さい方です.

Init_casts();

VALUE rubyval = cast_value(res, 1, 6); // Get the ruby type in row 1, column 6
于 2012-09-21T16:24:11.090 に答える