web-dev-qa-db-ja.com

COPYのバイナリ形式を解析してtsrangeにアクセスする

Tsrangeはどのようにバイナリで保存されますか?

たとえば、テーブルを作成する

CREATE TABLE public.test (t tsrange);
INSERT INTO test VALUES ('[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT INTO test VALUES ('[2011-01-01 14:31, 2015-11-01 15:30)');
INSERT INTO test VALUES ('[2017-01-01 14:31, 2018-11-01 15:30)');
COPY test TO '/tmp/pgcopy' WITH (FORMAT binary);
COPY test TO '/tmp/pgcopy.csv' WITH (FORMAT csv);

それは出力します:

 cat /tmp/pgcopy.csv                                                                                                                                                                                                  
"[""2010-01-01 14:30:00"",""2010-01-01 15:30:00"")"
"[""2011-01-01 14:31:00"",""2015-11-01 15:30:00"")"
"[""2017-01-01 14:31:00"",""2018-11-01 15:30:00"")"


hexdump -C /tmp/pgcopy
00000000  50 47 43 4f 50 59 0a ff  0d 0a 00 00 00 00 00 00  |PGCOPY..........|
00000010  00 00 00 00 01 00 00 00  19 02 00 00 00 08 00 01  |................|
00000020  1f 19 f9 a9 aa 00 00 00  00 08 00 01 1f 1a d0 3d  |...............=|
00000030  4e 00 00 01 00 00 00 19  02 00 00 00 08 00 01 3b  |N..............;|
00000040  c8 89 51 11 00 00 00 00  08 00 01 c6 7b 1a 3a 0e  |..Q.........{.:.|
00000050  00 00 01 00 00 00 19 02  00 00 00 08 00 01 e8 08  |................|
00000060  0d 77 11 00 00 00 00 08  00 02 1c 9a dc 4d 0e 00  |.w...........M..|
00000070  ff ff                                             |..|
00000072

1つのフィールドは次のとおりです。

00 00 00 19 02 00 00 00 08 00 01 e8 08 0d 77 11 00 00 00 00 08  00 02 1c 9a dc 4d 0e 00

そこ:

00000019-25バイトの長さ

02-ブラケット

00000008-サブフィールドの長さ

0001e808 0d771100および00021c9a dc4d0e00-ミリ秒で保存されたタイムスタンプ。

それを整数のタイムスタンプに変換する方法は?

2
eri

マイナーなメモとして、COPY .. (WITH BINARY)には角かっこはありません。これはフラグです(特に括弧を表します)。

COPY ... (WITH BINARY)

から COPYのドキュメント

実際のタプルデータの適切なバイナリ形式を決定するには、PostgreSQLソース、特に各列のデータ型の*send関数と*recv関数を調べてください(通常、これらの関数はソース配布のsrc/backend/utils/adt/ディレクトリにあります)。

さらに、ドキュメントはバイナリフォーマットが(現在)持っていると言います

  • 11バイトの署名
  • フラグ用に4バイト
  • 4バイトの可能性可変幅フィールドは現在使用されていないため、サイズ(4バイト\0\0\0\0)をスキップします。これらの4バイトに15があった場合、4バイトだけでなく、追加の15もスキップする必要があります。

次にタプルは

  • フィールドカウント用に2バイト

次に、フィールドがあります

  • 4バイト長の修飾子とそれに続くその数バイトのフィールドデータ。 (すでにtimestampまたはtsrangeの場合)

したがって、最初の列に到達するために基本的に25バイトをスキップします

tsrange

したがって、それは range_send で指定された形式になっています。上記のコメントで以下のビットで説明されていることがわかります range_recv

バイナリ表現:最初のバイトはフラグ、次に下限(存在する場合)、次に上限(存在する場合)です。 各境界は、4バイトの長さのヘッダーとその境界のバイナリ表現で表現されます(サブタイプのsend関数の呼び出しによって返されます) 。

timestampサブタイプ

あなたの場合、そのサブタイプはタイムスタンプであり、送信は timestamp_send です。

タイムスタンプは 8バイトとして保存 であることがわかります。これは単純なpq_sendint64(64ビット/ 8バイトの整数)で送信されるだけです。 timestamp_recv のしくみを読んで、タイムスタンプのバイナリ表現をどのように処理するかを確認する必要があります。ヒント: timestamp2tm のメモリ内表現の構造体に入ります

/* timestamp2tm()
 * Convert timestamp data type to POSIX time structure.
 * Note that year is _not_ 1900-based, but is an explicit full value.
 * Also, month is one-based, _not_ zero-based.
 * Returns:
 *   0 on success
 *  -1 on out of range

ここではこれ以上楽しませるつもりはありませんが、多分次に行きましょう。

遊んで

最初に DEADBEEF 8バイトマーカーを追跡するための分離を試みます。

psql -d test -c 'COPY ( SELECT E'\''DEADBEEF'\'' ) TO STDOUT WITH ( FORMAT BINARY );' |
  od --skip-bytes=25 --endian big --read-bytes=8 -c

今、それを交換します。

psql -d test -c 'COPY ( SELECT $$2010-01-01 14:30$$::timestamp without time zone ) TO STDOUT WITH ( FORMAT BINARY );' |
  od --skip-bytes=25 --endian big --read-bytes=8 --format=d8 -x

結果:括弧コメントが追加されました。

0000031      315671400000000  (timestamp in int8)
         0001 1f19 f9a9 aa00  (hex representation)
0000041

そして、それはあなたの最初のタイムゾーンの番号です。上記のセクションのようにtsrangeについては、

  • 間隔の1バイト
  • 上位:4バイト(ヘッダー)+ 8バイト(タイムスタンプ)
  • 下位:4バイト(ヘッダー)+ 8バイト(タイムスタンプ)

したがって、最初の内部タイムスタンプにアクセスするために、合計25バイトのスキップに加えて、合計5バイトをスキップします。

psql -d test -c 'COPY ( SELECT $$[2010-01-01 14:30, 2010-01-01 15:30)$$::tsrange ) TO STDOUT WITH ( FORMAT BINARY );' |
  od --skip-bytes=30 --endian big --read-bytes=8 --format=d8 -x

これにより、上記と同じ結果が得られます。

0000036      315671400000000
         0001 1f19 f9a9 aa00
0000046

--skip-bytesを42に変更して、その8バイトのタイムスタンプと、lowerの次の4バイトのヘッダーをスキップすると、別のタイムスタンプが取得されます。

1
Evan Carroll