32.9. COPY命令相关的函数

PostgreSQL中的COPY命令有用于libpq的对网络连接读出或者写入的选项。这一节描述的函数允许应用通过提供或者消耗已拷贝的数据来充分利用这个功能。

整个处理是应用首先通过PQexec或者一个等效的函数发出 SQL COPY命令。对这个命令的响应(如果命令无误)将是一个状态代码是PGRES_COPY_OUT或 者PGRES_COPY_IN(取决于指定的拷贝方向)的PGresult对象。应用然后就应该使用这一节的函数接收或者传送数据行。在数据传输结束之后,另外一个PGresult对象会被返回以表明传输的成功或者失败。它的状态将是:PGRES_COMMAND_OK表示成功,PGRES_FATAL_ERROR表示发生了一些问题。此时我们可以通过PQexec发出进一步的 SQL 命令(在COPY操作的处理过程中,不能用同一个连接执行其它 SQL 命令)。

如果一个COPY命令是通过PQexec在一个可能包含额外命令的字符串中发出的,那么应用在完成COPY序列之后必须继续用PQgetResult取得结果。只有在PQgetResult返回NULL时,我们才能确信PQexec的命令字符串已经处理完毕, 并且可以安全地发出更多命令。

这一节的函数应该只在从PQexecPQgetResult获得了PGRES_COPY_OUTPGRES_COPY_IN结果状态的后执行。

一个承载了这些状态值之一的PGresult对象携带了正在开始的COPY操作的一些额外数据。这些额外的数据可以用于那些与带查询结果的连接一起使用的函数:

PQnfields

返回要拷贝的列(域)的个数。

PQbinaryTuples

0 表示整体拷贝格式都是文本(行用新行分隔,列用分隔字符分隔等等)。1 表示整体拷贝格式都是二进制。详见COPY

PQfformat

返回与拷贝操的每列相关的格式代码(0 是文本,1 是二进制)。当整体拷贝格式是文本时,那么每列的格式代码将总是零,但是二进制格式可以同时支持文本和二进制列(不过,就目前的COPY实现而言,二进制拷贝中只会出现二进制列;所以目前每列的格式总是匹配总体格式)。

注意: 这些额外的数据值只在使用协议 3.0 时可用。在使用协议 2.0 时,所有这些函数都返回 0。

32.9.1. 用于发送COPY数据的函数

这些函数用于在COPY FROM STDIN期间发送数据。如果在连接不是COPY_IN状态,调用它们会失败。

PQputCopyData

COPY_IN状态中向服务器发送数据。

int PQputCopyData(PGconn *conn,
                  const char *buffer,
                  int nbytes);

传输指定buffer中长度为nbytesCOPY数据到服务器。如果数据排队,结果为1, 如果由于填满缓冲区而未排队(这只会发生在非阻塞模式),则结果为零, 如果发生错误,则结果为-1。(如果返回值为 -1,那么使用 PQerrorMessage检索细节。如果值是零, 那么等待写准备好然后重试)。

应用可以把COPY数据流划分成任意方便的大小放到缓冲区中。在发送时,缓冲区载荷的边界没有什么语意。数据流的内容必须匹配COPY命令预期的数据格式;详见COPY

PQputCopyEnd

COPY_IN状态中向服务器发送数据结束的指示。

int PQputCopyEnd(PGconn *conn,
                 const char *errormsg);

如果errormsgNULL,则成功结束COPY_IN操作。如果errormsg不是NULLCOPY被强制失败,errormsg指向的字符串是错误消息(不过,我们不应假定这个准确的错误信息将会从服务器传回,因为服务器可能已经因为其自身原因导致COPY失败。还要注意的是在使用 3.0 协议之前的连接时,强制失败的选项是不能用的)。

如果发送了终止消息,结果为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仍在处理过程中并且没有可用的完整行,那么它将返回零(在这种情况下等待读准备好,然后在再次调用PQgetCopyData之前,调用PQconsumeInput)。当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响应之后,应用应该调用PQconsumeInputPQgetlineAsync直到检测到结束数据的信号。

不像PQgetline,这个函数负责检测结束数据。

在每次调用时,如果libpq的输入缓冲区中有一个完整的数据行可用,PQgetlineAsync都将返回数据。否则,在剩余行到达之前不会返回数据。如果识别到拷贝数据结束的标志,此函数返回 -1;如果没有可用数据则返回 0; 或者是给出一个正数给出被返回的字节数。如果返回 -1,调用者下一步必须调用PQendcopy,然后回到正常处理。

返回的数据将不超过一个数据行的范围。如果可能,每次将返回一个完整行。但如果调用者提供的缓冲区太小不足以容下服务器发送的行,那么将返回部分行。对于文本数据,这可以通过测试返回的最后一个字节是否\n来检测(在二进制COPY中, 需要对COPY数据格式进行实际的分析,以便做相同的判断)。被返回的字符串不是空结尾的(如果你想增加一个终止空,确保传递一个比实际可用空间少一字节的bufsize)。

PQputline

向服务器发送一个空终止的字符串。如果 OK 则返回 0;如果不能发送字符串则返回EOF

int PQputline(PGconn *conn,
              const char *string);

一系列PQputline调用发送的COPY数据流和PQgetlineAsync返回的数据具有相同的格式, 只是应用不需要每次PQputline调用中发送刚好一个数据行;在每次调用中发送多行或者部分行都是可以的。

注意: PostgreSQL协议 3.0 之前,应用必须显式地发送两个字符\.作为最后一行来指示服务器已经完成发送COPY数据。虽然这么做仍然有效,但是它已经被废弃并且\.的特殊含义可能在将来的版本中删除。在发送完实际数据之后, 调用PQendcopy就足够了。

PQputnbytes

向服务器发送一个非空终止的字符串。如果 OK 则返回 0,如果不能发送字符串则返回EOF

int PQputnbytes(PGconn *conn,
                const char *buffer,
                int nbytes);

这个函数类似PQputline,除了数据缓冲区不需要是空终止,因为要发送的字节数是直接指定的。在发送二进制数据时使用这个过程。

PQendcopy

与服务器同步。

int PQendcopy(PGconn *conn);

这个函数等待服务器完成拷贝。当最后一个字符串已经用PQputline发送给服务器时或者当最后一个字符串已经用PGgetline从服务器接收到时,就会发出这个函数。这个函数必须被发出,否则服务器将会和客户端"不同步"。从这个函数返回后,服务器就已经准备好接收下一个 SQL 命令了。函数成功完成时返回值为 0,否则返回非零值(如果返回值为非零值,用PQerrorMessage检索详情)。

在使用PQgetResult时,应用应该通过反复调用PQgetline并且在看到终止行后调用PQendcopy来响应PGRES_COPY_OUT结果。然后它应该返回到PQgetResult循环直到PQgetResult返回一个空指针。类似地,PGRES_COPY_IN结果会用一系列PQputline加上之后的PQendcopy来处理,然后返 回到PQgetResult循环。这样的安排将保证嵌入到一系列SQL命令中的COPY命令将被正确执行。

旧的应用很可能会通过PQexec提交一个COPY命令并且假定事务在PQendcopy之后完成。只有在COPY是命令字符串中唯一的SQL命令时才能正确工作。