解析 COPY 的二进制格式以访问 tsrange

eri*_*eri 4 postgresql dump date-format fields copy

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);
Run Code Online (Sandbox Code Playgroud)

它输出:

 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
Run Code Online (Sandbox Code Playgroud)

其中一个字段是:

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
Run Code Online (Sandbox Code Playgroud)

那里:

00000019- 长度为 25 字节

02- 括号

00000008- 子字段长度

0001e808 0d771100-00021c9a dc4d0e00存储时间戳(以微秒为单位)。

如何将其转换为整数时间戳?

Eva*_*oll 5

作为一个小注释,COPY .. (WITH BINARY)没有括号。这是标志(除其他外还代表括号)。

COPY ... (WITH BINARY)

文档开始COPY

要确定实际元组数据的适当二进制格式,您应该查阅 PostgreSQL 源代码,特别是每列数据类型的*send和函数(通常这些函数可以在源代码分发的目录*recv中找到)。src/backend/utils/adt/

此外,文档说二进制格式(当前)有

  • 11字节签名
  • 4 个字节用于标志
  • 4 字节潜在可变宽度字段,当前未使用,因此我们跳过大小(4 字节\0\0\0\0),这在技术上并不好。如果这四个字节有 15,我们不仅要跳过这四个字节,还要跳过另外一个 15。

那么元组有

  • 2 个字节用于字段计数

然后字段有

  • 4 字节长度限定符,后跟相应字节的字段数据。timestamp(我们已经在或 的情况下tsrange

所以本质上我们跳过 25 个字节到达第一列

tsrange

所以它的格式是由range_send您可以在上面的评论中看到下面的解释range_recv

二进制表示:第一个字节是 flags,然后是下限(如果存在),然后是上限(如果存在)。 每个边界由 4 字节长度的标头和该边界的二进制表示形式表示(通过调用子类型的发送函数返回)。

亚型timestamp

在您的情况下,该子类型是时间戳,发送是 timestamp_send.

您可以看到时间戳存储为 8 个字节,并且只是使用简单的pq_sendint64(64 位/8 字节 int)发送。您必须阅读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
Run Code Online (Sandbox Code Playgroud)

我不会在这里对此进行更多的娱乐,但也许接下来会发生。

玩弄

我们首先尝试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
Run Code Online (Sandbox Code Playgroud)

现在我们把它换掉..

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
Run Code Online (Sandbox Code Playgroud)

结果:添加了括号注释。

0000031      315671400000000  (timestamp in int8)
         0001 1f19 f9a9 aa00  (hex representation)
0000041
Run Code Online (Sandbox Code Playgroud)

这就是您的第一个时区的号码。根据tsrange上面的部分,我们有

  • 间隔内一个字节
  • 上部:4字节(标头)+8字节(时间戳)
  • 低:4字节(标头)+8字节(时间戳)

因此,为了访问第一个内部时间戳,我们在已经跳过 25 个字节的基础上再跳过 5 个字节,总共 30 个字节。

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
Run Code Online (Sandbox Code Playgroud)

这给了我们与上面相同的结果..

0000036      315671400000000
         0001 1f19 f9a9 aa00
0000046
Run Code Online (Sandbox Code Playgroud)

只需更改--skip-bytes为 42 即可跳过该 8 字节时间戳以及接下来的 4 字节标头,lower您将获得另一个时间戳。