web-dev-qa-db-ja.com

postgres byteaフィールドのバイト順を逆にする

私は現在、ハッシュを含み、bytea形式で格納されているテーブルに取り組んでいます。ただし、ハッシュを16進文字列に変換すると、バイトの順序が正しくなくなります。例:

SELECT encode(hash, 'hex') FROM mytable LIMIT 1;

Output: 1a6ee4de86143e81
Expected: 813e1486dee46e1a

すべてのエントリのバイトの順序を逆にする方法はありますか?

6
R. Martin

これを行う1つの方法を次に示しますが、これを行うことは決してありません。データベースのbytea列にバイトを格納することに問題はありません。しかし、私はデータベースに少し絡みませんでした。

  • a C言語関数 、または
  • 入力をバイトのセットに分解する必要のない、いくつかの派手な手続き型言語。

これはsql-esqueであり、機能するはずです-これが私たちがやっていることです、

  1. 一連のオフセット0 bytelength-1)で構成されるセットを生成します。
  2. これらのオフセットを、16進数の文字列として表されるバイトにマッピングします。
  3. 文字列は逆の順序でそれらを集約します。

ここに例があります

CREATE TABLE foo AS SELECT '\x813e1486dee46e1a'::bytea AS bar;

SELECT bar, string_agg(to_hex(byte), '') AS hash
FROM foo
CROSS JOIN LATERAL (
  SELECT get_byte(bar,"offset") AS byte
  FROM generate_series(0,octet_length(bar)-1) AS x("offset")
  ORDER BY "offset" DESC
) AS x
GROUP BY bar;

2つのメモ

  1. ---(予約済み であるため、おそらくoffsetを使用できませんが、要点はわかります。
  2. これは、ハッシュ(上のバー)が一意であることを前提としています。
5
Evan Carroll

エンコードされた表現をテキストとして扱い、正規表現を使用してバイト単位で逆にすることができます。

SELECT string_agg(reverse(b[1]),'')
FROM regexp_matches(reverse(encode('STUFF','hex')),'..','g')b;

別の(より冗長な)メソッド:

WITH bytes AS (
  SELECT row_number() over() AS n, byte[1]
  FROM regexp_matches( encode( 'STUFF', 'hex' ), '..', 'g' ) AS byte
), revbytes AS (
  SELECT * FROM bytes ORDER BY n DESC
)
SELECT array_to_string(array_agg(byte),'')
FROM revbytes;

使用例:

(filip@[local:/var/run/postgresql]:5432) filip=# SELECT encode( 'STUFF', 'hex' );
   encode   
------------
 5354554646
(1 row)

(filip@[local:/var/run/postgresql]:5432) filip=# SELECT string_agg(reverse(b[1]),'')FROM regexp_matches(reverse(encode('STUFF','hex')),'..','g')b;
 string_agg 
------------
 4646555453
(1 row)
3
filiprem

bytea値のバイトを逆にするだけでよい場合は、 plpythonu を使用する(比較的)シンプルで高速なソリューションがあります。

create or replace function reverse_bytea(p_inp bytea) returns bytea stable language plpythonu as $$
  b = bytearray()
  b.extend(p_inp)
  b.reverse()
  return b
$$;

select encode(reverse_bytea('\x1a6ee4de86143e81'), 'hex');
----
813e1486dee46e1a

しかし、私はデータ自体に何か問題があると思います(ストレージの方法、データの解釈...)

3
Abelisto

Vanilla Postgresのツールを使用したソリューション:

両方のソリューションに列_bytea_reverse_を追加しました。必要がなければ削除してください。

get_byte() の場合:

_SELECT t.b, text_reverse, decode(text_reverse, 'hex') AS bytea_reverse
FROM   tbl t
LEFT   JOIN LATERAL (
   SELECT string_agg(to_hex(get_byte(b, x)), '') AS text_reverse
   FROM   generate_series(octet_length(t.b) - 1, 0, -1) x
   ) x ON true;
_

これは @ Evanが提供したもの に似ています。彼の優れた説明のほとんどが当てはまります。だが:

  • _LEFT JOIN LATERAL ... ON true_を使用すると、NULL値を持つ行が失われます。
  • generate_series()は逆数を提供できるため、別の_ORDER BY_ステップは必要ありません。
  • LATERAL結合を使用しながら、サブクエリで集計します。エラーが発生しにくく、より複雑なクエリと統合しやすく、外部クエリで_GROUP BY_を使用する必要がありません。

regexp_matches()の場合:

_SELECT t.b, text_reverse, decode(text_reverse, 'hex') AS bytea_reverse
FROM   tbl t
LEFT   JOIN LATERAL (
   SELECT string_agg(byte[1], '' ORDER  BY ord DESC) AS text_reverse
   FROM   regexp_matches(encode(t.b, 'hex' ), '..', 'g' ) WITH ORDINALITY AS x(byte, ord)
   ) x ON true;
_

これは "verbose"バリアント@filipremが提供される に似ています。だが:

  • _LEFT JOIN LATERAL ... ON true_を使用すると、NULL値を持つ行が失われます。
  • _WITH ORDINALITY_を使用して、「無料」で行番号を取得します。したがって、row_number()を使用した別のサブクエリも、二重のreverse()も必要ありません。詳細:
  • 集約関数で逆順を実行できます。 (ただし、サブクエリで順序付けし、別のサブクエリレイヤーを追加して、事前に順序付けされた行を集計する方が少し高速かもしれません。)
  • 通常、2つのCTEではなく1つのサブクエリ(または2つ)の方が高速です。

SOで同様の質問:

3

すべての提案のおかげで、私は必要に応じて機能するこのC言語関数を書きました:

#include "postgres.h"
#include "fmgr.h"

#ifdef PG_MODULE_MAGIC
    PG_MODULE_MAGIC;
#endif

Datum bytea_custom_reverse(PG_FUNCTION_ARGS);

PG_FUNCTION_INFO_V1(bytea_custom_reverse);
Datum
bytea_custom_reverse(PG_FUNCTION_ARGS) {
  bytea *data = PG_GETARG_BYTEA_P_COPY(0);
  unsigned char *ptr = (unsigned char *) VARDATA(data);

  int32 dataLen = VARSIZE(data) - VARHDRSZ;

  unsigned char *start, *end;

  for ( start = ptr, end = ptr + dataLen - 1; start < end; ++start, --end ) {
    unsigned char swap = *start;
    *start = *end;
    *end = swap;
  }


  PG_RETURN_BYTEA_P(data);
}
1
R. Martin

このスレッドを助けてくれてありがとう。そして、これは、回答に応じて、BitConverter.GetBytes()を使用して、C#のようなlittleEndianでbigintをbyteaに変換するという私の選択です。

with mycte as (
select int8send(394112768534335::bigint) as conversionValue
)
SELECT decode(string_agg (
  (case when get_byte(conversionValue, x)<= 15 then ('0')  else  '' end) ||
  to_hex(get_byte(conversionValue, x))
  , ''), 'hex') AS nativeId_reverse  
   FROM mycte,  generate_series(octet_length(conversionValue) - 1, 0, -1) as x;

BigintプレゼンテーションによってlittleEndian byteAとしてpostgresqlに配置された検索値の場合:

with mycte as (
select int8send(394112768534335::bigint) as conversionValue
)
Select * FROM mycte, *SomeByteaFieldTable*
where *SomeByteaId* =                                                                         
(SELECT decode(string_agg (
  (case when get_byte(conversionValue, x)<= 15 then ('0')  else  '' end) ||
  to_hex(get_byte(conversionValue, x))
  , ''), 'hex') AS nativeId_reverse  
   FROM   generate_series(octet_length(conversionValue) - 1, 1, -1) x);