postgres_proto是一个python编写的模拟接收postgres协议请求的服务器。
pgproto是一个c编写的发送postgres协议请求的客户端。
但是两者的文档都不很详细,源代码中也有错误。通过查看报错信息,修改postgres_proto解析器源码,成功实现了两者对话。修改pgproto源码增加了数据输出函数,不但保证消息的正确性,还能查看消息的内容。
在parser.py
中注释掉非必须的SQL成分,如果需要测试相应的子句,可以加上。
SelectStmt = namedtuple('SelectStmt', ['columns', 'tables']) #,'cols_aliases', 'where', 'group_by', 'order_by' 'limit', 'offset'])
SelectColumnExpr = namedtuple('SelectColumnExpr', ['name', 'alias'])
FromTableExpr = namedtuple('FromTableExpr', ['name', 'schema', 'alias']) #, 'joins', 'subquery'])
在read.c中添加如下函数,并在数据行的处理中添加调用
void parse_structured_data(const unsigned char* buf, size_t len) {
if (len < 2) return;
int pos = 0;
int is_first_field = 1;
// 读取字段数量(2字节,大端序)
uint16_t field_count = (buf[0] << 8) | buf[1];
pos += 2;
//printf("字段数量: %u\n", field_count);
for (int i = 0; i < field_count; i++) {
if (pos + 4 > len) break; // 检查边界
// 读取字段长度(4字节,大端序)
uint32_t field_len = (buf[pos] << 24) | (buf[pos+1] << 16) | (buf[pos+2] << 8) | buf[pos+3];
pos += 4;
if (pos + field_len > len) break; // 检查边界
// 输出字段分隔符(除了第一个字段)
if (!is_first_field) {
fprintf(stderr,"|");
}
is_first_field = 0;
// 输出字段内容
for (uint32_t j = 0; j < field_len; j++) {
fprintf(stderr,"%c", buf[pos + j]);
}
pos += field_len;
}
fprintf(stderr,"\n");
}
...
void read_until_ready_for_query(PGconn *conn, int timeout)
{
...
case 'D': /* Data row */
fprintf(stderr, "<= BE DataRow\n");
len = read_int32(conn);
buf = read_bytes(len - sizeof(int), conn);
parse_structured_data(buf, len);
pg_free(buf);
//read_and_discard(conn);
break;
...
编写pgproto测试命令脚本, 其中Q行语句可以改为"SELECT * FROM table1"
以测试静态数据。
#
# Test data example
#
'Q' "SELECT * FROM csv"
'Y'
'P' "S1" "BEGIN" 0
'B' "" "S1" 0 0 0
'E' "" 0
'C' 'S' "S1"
'P' "foo" "SELECT 1" 0
'B' "myportal" "foo" 0 0 0
'E' "myportal" 0
'P' "S2" "COMMIT" 0
'B' "" "S2" 0 0 0
'E' "" 0
'C' 'S' "S2"
'S'
'Y'
'X'
测试python代码中的静态数据
服务端,在examples目录下执行命令行,其中PYTHONPATH=..
是告诉python3 postgres_proto模块在上一级目录下。这是DeepSeek教我的,否则按照README文档$ python -m postgres_proto.server your_file.MyRequestHandler
是不能执行成功的。
/par/postgres-proto/examples# PYTHONPATH=.. python3 static.py
Serving on 127.0.0.1:55432
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 41856)
客户端,用.configure和make编译
/par/pgproto# src/pgproto -h 127.0.0.1 -p 55432 -f /par/pgpo.txt
FE=> Query (query="SELECT * FROM table1")
<= BE RowDescription
<= BE DataRow
1|hello world
<= BE DataRow
2|my second row
<= BE CommandComplete(SELECT)
<= BE ReadyForQuery(I)
FE=> Parse(stmt="S1", query="BEGIN")
FE=> Bind(stmt="S1", portal="")
FE=> Execute(portal="")
FE=> Close(stmt="S1")
FE=> Parse(stmt="foo", query="SELECT 1")
FE=> Bind(stmt="foo", portal="myportal")
FE=> Execute(portal="myportal")
FE=> Parse(stmt="S2", query="COMMIT")
FE=> Bind(stmt="S2", portal="")
FE=> Execute(portal="")
FE=> Close(stmt="S2")
FE=> Sync
<= BE ParseComplete
<= BE BindComplete
<= BE CommandComplete(BEGIN)
<= BE CloseComplete
<= BE ParseComplete
<= BE BindComplete
read_it: EOF detect
测试外部csv文件中的数据
服务端
/par/postgres-proto/examples# PYTHONPATH=.. python3 csv_db.py demo.csv
Serving on 127.0.0.1:55432
----------------------------------------
Exception occurred during processing of request from ('127.0.0.1', 44076)
客户端
/par/pgproto# src/pgproto -h 127.0.0.1 -p 55432 -f /par/pgpo.txt
FE=> Query (query="SELECT * FROM csv")
<= BE RowDescription
<= BE DataRow
toto@toto.com|toto|1111
<= BE DataRow
titi@titi.com|titi|2222
<= BE DataRow
tutu@tutu.com|tutu|
<= BE CommandComplete(SELECT)
<= BE ReadyForQuery(I)
FE=> Parse(stmt="S1", query="BEGIN")
FE=> Bind(stmt="S1", portal="")
FE=> Execute(portal="")
FE=> Close(stmt="S1")
FE=> Parse(stmt="foo", query="SELECT 1")
FE=> Bind(stmt="foo", portal="myportal")
FE=> Execute(portal="myportal")
FE=> Parse(stmt="S2", query="COMMIT")
FE=> Bind(stmt="S2", portal="")
FE=> Execute(portal="")
FE=> Close(stmt="S2")
FE=> Sync
<= BE ParseComplete
<= BE BindComplete
<= BE CommandComplete(BEGIN)
<= BE CloseComplete
<= BE ParseComplete
<= BE BindComplete
read_it: EOF detected
协议的字母含义参阅postgresql文档。上述自定义函数的结构来自以下说明。
DataRow (B)
Byte1('D')
标识消息为数据行。
Int32
消息内容(包括自身)的长度(以字节为单位)。
Int16
下面跟随着的列值数量(可能为零)。
接下来,每列出现以下一对字段
Int32
列值的长度,以字节为单位(此计数不包括自身)。可以为零。在特殊情况下,-1 表示 NULL 列值。在 NULL 情况下,不跟随着任何值字节。
Byten
列的值,格式由关联的格式代码指示。n 是上面的长度。
暂时只考虑字符串类型。
补记,postgresql的psql客户端也能访问此服务端
/par# psql -h 127.0.0.1 -p 55432
psql (15.13 (Debian 15.13-0+deb12u1), server 130000)
WARNING: psql major version 15, server major version 130000.
Some psql features might not work.
Type "help" for help.
root=> select * from table1;
id | title
----+---------------
1 | hello world
2 | my second row
(2 rows)
root=> select * from table2;
id | name
----+-------------------------
1 | first row, second table
(1 row)
root=> select * from csv;
email | name | phone
---------------+------+-------
toto@toto.com | toto | 1111
titi@titi.com | titi | 2222
tutu@tutu.com | tutu |
(3 rows)