32.9. COPY 命令相关的函数 #
PostgreSQL 的 COPY 命令提供了选项,可以通过 libpq 使用的网络连接读取或写入数据。本节介绍的函数允许应用程序通过提供或接收复制数据来使用这一能力。
整体流程如下:应用程序先通过 PQexec 或等效函数发出 SQL COPY 命令。如果命令没有错误,响应就是一个 PGresult 对象,其状态码为 PGRES_COPY_OUT 或 PGRES_COPY_IN,取决于指定的复制方向。应用程序随后应使用本节函数接收或发送数据行。数据传输完成后,会返回另一个 PGresult 对象,表示传输成功或失败:成功时状态为 PGRES_COMMAND_OK,出现问题时为 PGRES_FATAL_ERROR。此时可以通过 PQexec 继续发出 SQL 命令。(COPY 操作进行期间,不能在同一连接上执行其他 SQL 命令。)
如果一个 COPY 命令是通过 PQexec 在一个可能包含额外命令的字符串中发出的,那么应用在完成 COPY 序列之后必须继续用 PQgetResult 取得结果。只有在 PQgetResult 返回 NULL 时,我们才能确信 PQexec 的命令字符串已经处理完毕,并且可以安全地发出更多命令。
只有从 PQexec 或 PQgetResult 获得 PGRES_COPY_OUT 或 PGRES_COPY_IN 结果状态后,才应调用本节函数。
带有上述某个状态值的 PGresult 对象,还会携带关于即将开始的 COPY 操作的附加数据。这些数据可以通过下列函数获取,这些函数也用于查询结果:
32.9.1. 用于发送 COPY 数据的函数 #
这些函数用于在 COPY FROM STDIN 期间发送数据。如果连接不处于 COPY_IN 状态,调用它们会失败。
PQputCopyData#在
COPY_IN状态中向服务器发送数据。int PQputCopyData(PGconn *conn, const char *buffer, int nbytes);将指定
buffer中长度为nbytes的COPY数据传输到服务器。数据成功加入队列时返回 1;因缓冲区已满而无法加入队列时返回零(仅可能发生在非阻塞模式下);发生错误时返回 -1。(返回 -1 时,可用PQerrorMessage获取详细信息。返回零时,应等待可写就绪后重试。)应用程序可以将
COPY数据流分成任意方便大小的数据块,逐块装入缓冲区。发送时,这些数据块的边界没有语义含义。数据流内容必须符合COPY命令预期的数据格式;详见 COPY。PQputCopyEnd#在
COPY_IN状态中向服务器发送数据结束的指示。int PQputCopyEnd(PGconn *conn, const char *errormsg);如果
errormsg为NULL,则成功结束COPY_IN操作。如果errormsg不为NULL,则强制COPY失败,并将errormsg指向的字符串用作错误消息。(但不应假定服务器一定会返回这条完全相同的错误消息,因为服务器可能已经因自身原因使COPY失败。)终止消息已发送时返回 1;在非阻塞模式下,返回 1 也可能仅表示该消息已成功加入发送队列。(在非阻塞模式下,要确认数据已经发送,应接着等待可写就绪并调用
PQflush,反复执行直到返回零。)返回零表示缓冲区已满,无法将终止消息加入队列;这种情况仅可能发生在非阻塞模式下。(此时,应等待可写就绪,再次调用PQputCopyEnd。)发生严重错误时返回 -1,可用PQerrorMessage获取详细信息。成功调用
PQputCopyEnd后,调用PQgetResult获取COPY命令的最终结果状态。可以按通常方式等待该结果就绪,然后恢复正常操作。
32.9.2. 用于接收 COPY 数据的函数 #
这些函数用于在 COPY TO STDOUT 的过程中接收数据。如果连接不在 COPY_OUT 状态,那么调用它们将会失败。
PQgetCopyData#在
COPY_OUT状态下从服务器接收数据。int PQgetCopyData(PGconn *conn, char **buffer, int async);在
COPY期间尝试从服务器获取下一行数据。每次总是返回一个完整数据行;如果只有部分行可用,则不返回。成功返回数据行时,会分配一块内存保存数据。buffer参数必须为非NULL。*buffer会被设置为指向所分配的内存;如果没有返回缓冲区,则设为NULL。非NULL的结果缓冲区在不再需要时应使用PQfreemem释放。成功返回一行时,返回值是该行的数据字节数,始终大于零。返回的字符串总是以零字节结尾,不过这可能仅对文本
COPY有用。返回零表示COPY仍在进行,但尚无可用行(仅在async为真时可能发生)。返回 -1 表示COPY已完成;返回 -2 表示发生了错误(可用PQerrorMessage查看原因)。当
async为真(非零)时,PQgetCopyData不会阻塞等待输入;如果COPY仍在进行,但没有完整行可用,则返回零。(此时,应等待读就绪,先调用PQconsumeInput,再调用PQgetCopyData。)当async为假(零)时,PQgetCopyData会阻塞,直到数据可用或操作完成。在
PQgetCopyData返回 -1 后,调用PQgetResult获取COPY命令的最终结果状态。可以按通常方式等待该结果就绪,然后恢复正常操作。
32.9.3. 用于 COPY 的过时函数 #
这些函数使用较旧的方式处理 COPY。虽然仍然可用,但由于错误处理欠佳、检测数据结束的方式不便,而且缺少对二进制或非阻塞传输的支持,已被弃用。
PQgetline#将服务器传来的、以换行符结尾的一行字符读入大小为
length的字符串缓冲区。int PQgetline(PGconn *conn, char *buffer, int length);此函数最多将
length-1 个字符复制到缓冲区,并将末尾的换行符转换为零字节。PQgetline在输入结束时返回EOF,读完一整行时返回 0,缓冲区已满但尚未读到末尾换行符时返回 1。注意,应用程序必须检查新读入的一行是否仅由
\.两个字符组成,这表示服务器已发送完COPY命令的结果。如果可能收到长度超过length-1 个字符的行,必须确保正确识别\.行,例如不能把长数据行的末尾误当作终止行。PQgetlineAsync#以非阻塞方式将服务器传来的一行
COPY数据读入缓冲区。int PQgetlineAsync(PGconn *conn, char *buffer, int bufsize);此函数类似于
PQgetline,但可用于必须异步读取COPY数据的应用程序,即读取时不阻塞。发出COPY命令并收到PGRES_COPY_OUT响应后,应用程序应调用PQconsumeInput和PQgetlineAsync,直到检测到数据结束信号。与
PQgetline不同,此函数会负责检测数据结束。每次调用时,如果 libpq 的输入缓冲区中有完整数据行,
PQgetlineAsync就会返回数据;否则,要等该行剩余部分到达后才返回数据。识别到复制数据结束标记时返回 -1,没有可用数据时返回 0,否则返回正数,表示返回的数据字节数。返回 -1 后,调用者必须接着调用PQendcopy,然后恢复正常处理。返回的数据不会跨越数据行边界。只要可能,每次就返回一整行;但如果调用者提供的缓冲区太小,容不下服务器发送的一行,则只返回部分行。对于文本数据,可检查最后返回的字节是否为
\n,以判断是否返回了完整行。(对于二进制COPY,则必须实际解析COPY数据格式才能作出相同判断。)返回的字符串不以零字节结尾。(如果要自行添加末尾的零字节,务必将传入的bufsize设置为比实际可用空间少一字节。)PQputline#向服务器发送以零字节结尾的字符串。成功时返回 0,无法发送字符串时返回
EOF。int PQputline(PGconn *conn, const char *string);连续调用
PQputline发送的COPY数据流,与PQgetlineAsync返回的数据格式相同。不过,应用程序不必在每次PQputline调用中恰好发送一个数据行;每次发送部分行或多行也可以。注意
在 PostgreSQL 协议 3.0 之前,应用程序必须显式发送由
\.两个字符组成的最后一行,告知服务器应用程序已发送完COPY数据。虽然这种方式仍然有效,但已被弃用,\.的特殊含义预计会在未来版本中移除。(在CSV模式下,这种做法已经会出现异常。)发送完实际数据后,调用PQendcopy即可。PQputnbytes#向服务器发送不以零字节结尾的字符串。成功时返回 0,无法发送字符串时返回
EOF。int PQputnbytes(PGconn *conn, const char *buffer, int nbytes);此函数与
PQputline完全相同,只是直接指定了要发送的字节数,因此数据缓冲区不必以零字节结尾。发送二进制数据时可使用此函数。PQendcopy#与服务器同步。
int PQendcopy(PGconn *conn);
此函数会等待服务器完成复制。应在使用
PQputline向服务器发送最后一个字符串后,或使用PQgetline从服务器接收最后一个字符串后调用它。必须调用此函数,否则服务器与客户端会“失去同步”。函数返回后,服务器便准备好接收下一条 SQL 命令。成功完成时返回 0,否则返回非零值。(返回非零值时,可用PQerrorMessage获取详细信息。)使用
PQgetResult时,收到PGRES_COPY_OUT结果后,应用程序应反复调用PQgetline,并在看到终止行后调用PQendcopy。随后应回到PQgetResult循环,直到PQgetResult返回空指针。类似地,收到PGRES_COPY_IN结果后,应连续调用PQputline,再调用PQendcopy,然后回到PQgetResult循环。这样可以保证嵌在一系列 SQL 命令中的COPY命令正确执行。旧的应用很可能会通过
PQexec提交一个COPY命令并且假定事务在PQendcopy之后完成。只有在COPY是命令字符串中唯一的 SQL 命令时才能正确工作。