利用postgres_proto和pgproto配合验证测试postgres协议

#【投稿赢 iPhone 17】「我的第一个开源项目」故事征集:用代码换C位出道!#

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值