文本方式Copy
1. 创建测试表
sde=# \d test
Table "sde.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |
2. 插入两条记录
string sql = "COPY TEST FROM stdin;";
PGresult* execresult = PQexec(m_pgconn, sql.c_str());
//服务器端执行DoCopy函数,并等待后续数据的到来
//获取执行状态
ExecStatusType execstatus = PQresultStatus(execresult);
if (PGRES_COPY_IN!= execstatus)
{
PQclear(execresult);
return -1;
}
发送实际数据,插入值是4,6,空三个值
sql = "4\ta";
sql += "\n";
sql += "6\t\\N";
sql += "\n";
sql += "\\N\tc";
sql += "\n";
//向数据库发送实际数据
int result = PQputCopyData(m_pgconn,sql.c_str(),sql.size());
if(1 != result)
{
char* error = PQerrorMessage(m_pgconn);
return -1;
}
//结束发送,导致后台继续执行DoCopy,并再循环中调用NextCopyFrom存储数据,该函数中会调用int4in函数
result = PQputCopyEnd(m_pgconn, error);
if(1 != result)
{
return -1;
}
execresult = PQgetResult(m_pgconn);
execstatus = PQresultStatus(execresult);
if(PGRES_COMMAND_OK != execstatus)
{
error = PQerrorMessage(m_pgconn);
PQclear(execresult);
return -1;
}
4. 结果
sde=# select * from test;
id | info
----+------
4 | a
6 |
| c
(3 rows)
二进制方式Copy
二进制头格式
摘自PostgreSQL文档
文件头
文件头由 15 字节的固定域构成,后面跟着一个变长的头部扩展区。 固定域有:
签名
11-字节的序列PGCOPY\n\377\r\n\0 — 注意 零字节是签名的一个必要的部分(该签名是为了能容易地发现文件被 无法正确处理 8 位字符编码的传输所破坏。这个签名将被行尾翻译过 滤器、删除零字节、删除高位或者奇偶修改等改变)。
标志域
32-位整数位掩码,用以表示该文件格式的重要方面。位被编号为 从 0 (LSB)到 31(MSB)。 注意这个域以网络字节序存放(最高有效位在前),所有该文件格式 中使用的整数域都是这样。16-31 位被保留用来表示严重的文件格式 问题, 读取者如果在这个范围内发现预期之外的被设置位,它应该 中止。0-15 位被保留用来表示向后兼容的格式问题,读取者应该简单 地略过这个范围内任何预期之外的被设置位。当前只定义了一个标志 位,其他位必须为零:
位 16
如果为 1,表示数据中包含 OID;如果为 0,则不包含。PostgreSQL不再支持Oid系统列,但是格式仍然包含该指示符。
头部扩展区长度
32-为整数,表示头部剩余部分的以字节计的长度,不包括其本身。 当前,这个长度为零,并且其后就紧跟着第一个元组。未来对该 格式的更改可能会允许在头部中表示额外的数据。如果读取者不知 道要对头部扩展区数据做什么,可以安静地跳过它。
头部扩展区域被预期包含一个能自我解释的块的序列。 该标志域并不想告诉读取者扩展数据是什么。详细的 头部扩展内容的设计留给后来的发行去做。
这种设计允许向后兼容的头部增加(增加头部扩展块或者设置低位标志位)以及 非向后兼容的更改(设置高位标志位来表示这类更改并且在需要时向扩展区域 中增加支持数据)。
元组
每一个元组由一个表示元组中域数量的 16 位整数计数开始(当前,一个表中 的所有元组都应该具有相同的计数,但是这可能不会总是为真)。然后是元组 中的每一个域,它是一个 32 位的长度字,后面则跟随着这么多个字节的域数 据(长度字不包括其本身,并且可以是零)。作为一种特殊情况,-1 表示一个 NULL 域值。在 NULL 情况下,后面不会跟随值字节。
在域之间没有对齐填充或者任何其他额外的数据。
当前,一个二进制格式文件中的所有数据值都被假设为二进制格式(格式代码一)。 可以预见未来的扩展可能会增加一个允许独立指定各列的格式代码的头部域。
要为实际的元组数据决定合适的二进制格式,你应该参考 PostgreSQL源码,特别是用于各列 数据类型的send和recv函数(通常可 以在源码的src/backend/utils/adt/目录中找到 这些函数)。
如果文件中包含 OID,OID 域会紧跟在域计数字之后。它是一个普通域, 不过它没有被包含在域计数中。注意PostgreSQL当前版本不支持oid系统列。
文件尾
文件位由一个包含 -1 的 16 位整数字组成。这很容易与一个 元组的域计数字区分开。
如果一个域计数字不是 -1 也不是期望的列数,读取者应该报告错误。 这提供了一种针对某种数据不同步的额外检查。
sde=# \d test
Table "sde.test"
Column | Type | Collation | Nullable | Default
--------+---------+-----------+----------+---------
id | integer | | |
info | text | | |
程序表示
string sql = "COPY TEST (id,info) FROM stdin with binary;";
execresult = PQexec(m_pgconn, sql.c_str());
ExecStatusType execstatus = PQresultStatus(execresult);
if (PGRES_COPY_IN != execstatus)
{
PQclear(execresult);
return -1;
}
char tempchar[1024];
//签名(11个字节)
unsigned char signature[] = {'P','G','C','O','P','Y', '\n',0xff,'\r','\n','\0'};
unsigned int offset = sizeof(signature);
memcpy(tempchar, signature, offset);
//标志域(如果表包含oid,16bit为1,否则为0,pg12已经没有取消oid因此为0)
unsigned int flagdomain = 0;
memcpy(tempchar + offset, &flagdomain, sizeof(unsigned int));
//头大小(保留字段,默认为0)
unsigned int headersize = 0;
offset += sizeof(unsigned int);
memcpy(tempchar + offset, &headersize, sizeof(unsigned int));
//字段数量,网络字节编码(每条记录都需要)
offset += sizeof(unsigned int);
unsigned short fieldcount = 2;
fieldcount = htons(fieldcount);
memcpy(tempchar + offset, &fieldcount, sizeof(unsigned short));
offset += sizeof(unsigned short);
//第一个字段大小(类型为int,因此为4个字节,网络字节编码)
unsigned int firstintsize = 4;
firstintsize = htonl(firstintsize);
memcpy(tempchar + offset, &firstintsize, sizeof(unsigned int));
offset += sizeof(unsigned int);
//第一个字段的值(100,网络字节编码)
int firstintval = 100;
firstintval = htonl(firstintval);
memcpy(tempchar + offset, &firstintval, sizeof(int));
offset += sizeof(int);
//第二个字段的值(文本类型,a,z两个值,一次大小为2,网络字节编码)
unsigned int secondfieldsize = 2;
secondfieldsize = htonl(secondfieldsize);
memcpy(tempchar + offset, &secondfieldsize, sizeof(unsigned int));
offset += sizeof(unsigned int);
//第二个字段值
char secondvalue[] = { 'a','z' };
memcpy(tempchar + offset, secondvalue, sizeof(secondvalue));
offset += sizeof(secondvalue);
//尾部信息(两个0xff)
unsigned char tailor[] = {0xff,0xff};
memcpy(tempchar + offset, tailor, sizeof(tailor));
offset += sizeof(tailor);
FILE* pfile = fopen("d:\\test.bin", "wb");
fwrite(tempchar, sizeof(char), offset,pfile);
fclose(pfile);
int result = PQputCopyData(m_pgconn,tempchar,offset);
char* error = PQerrorMessage(m_pgconn);
error = NULL;
result = PQputCopyEnd(m_pgconn, error);
execresult = PQgetResult(m_pgconn);
execstatus = PQresultStatus(execresult);
error = PQerrorMessage(m_pgconn);
Note:
后台调用int和text类型的二进制的输入和输出函数
test.bin的内容
[root@dataserver ~]# hexdump -c test.bin
0000000 P C C O P Y \n 377 \r \n \0 \0 \0 \0 \0 \0
0000010 \0 \0 \0 \0 002 \0 \0 \0 004 \0 \0 \0 d \0 \0 \0
0000020 002 a z 377 377
[root@dataserver ~]# hexdump -C test.bin
00000000 50 43 43 4f 50 59 0a ff 0d 0a 00 00 00 00 00 00 |PCCOPY..........|
00000010 00 00 00 00 02 00 00 00 04 00 00 00 64 00 00 00 |............d...|
00000020 02 61 7a ff ff |.az..|
参考
https://blog.csdn.net/inrgihc/article/details/104642238