PQexec
函数对于在普通的同步应用中提交命令是足以胜任的。不过,它的一些缺点可能对某些用户很重要:
不想受到这些限制的应用可以改用构建PQexec
的底层函数:PQsendQuery
以及PQgetResult
。还有
PQsendQueryParams
,
PQsendPrepare
,
PQsendQueryPrepared
,
PQsendDescribePrepared
, 和
PQsendDescribePortal
,
它们可以与 PQgetResult
一起使用来分别复制
PQexecParams
,
PQprepare
,
PQexecPrepared
,
PQdescribePrepared
, 和
PQdescribePortal
的函数。
PQsendQuery
向服务器提交一个命令而不等待结果。如果该命令被成功发送则返回 1,否则返回 0(此时,可以用PQerrorMessage
获取关于失败的信息)。
int PQsendQuery(PGconn *conn, const char *command);
在成功调用PQsendQuery
后,调用PQgetResult
一次或者多次来获取结果。
在PQsendQuery
返回一个空指针之前,都不能再次调用PQgetResult
,返回的空指针指示该命令已经完成。
在管道模式中,命令字符串中包含多余一条SQL命令是不允许的。
PQsendQueryParams
向服务器提交一个命令和单独的参数,而不等待结果。
int PQsendQueryParams(PGconn *conn, const char *command, int nParams, const Oid *paramTypes, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat);
这个函数等效于PQsendQuery
,不过查询参数可以独立于查询字符串分开指定。该函数的参数处理和PQexecParams
一样。
和PQexecParams
类似,它只允许在查询字符串中有一条命令。
PQsendPrepare
发送一个请求用给定参数创建一个预备语句,而不等待完成。
int PQsendPrepare(PGconn *conn, const char *stmtName, const char *query, int nParams, const Oid *paramTypes);
这个函数是PQprepare
的异步版本:如果它能发送这个请求,则返回 1;如果不能,则返回 0。
在成功调用之后,调用PQgetResult
判断服务器是否成功创建了预备语句。
这个函数的参数的处理和PQprepare
一样。
PQsendQueryPrepared
发送一个请求用给定参数执行一个预备语句,而不等待结果。
int PQsendQueryPrepared(PGconn *conn, const char *stmtName, int nParams, const char * const *paramValues, const int *paramLengths, const int *paramFormats, int resultFormat);
这个函数与PQsendQueryParams
类似,但是要执行的命令是通过一个之前已经命名的预备语句指定, 而不是一个给出的查询字符串。
该函数的参数处理和PQexecPrepared
一样。
PQsendDescribePrepared
发送一个请求获得指定的预备语句的信息,但不等待完成。
int PQsendDescribePrepared(PGconn *conn, const char *stmtName);
这个函数是PQdescribePrepared
的一个异步版本:如果它能够发送请求,则返回 1;否则,返回 0。
在一次成功的调用后,调用PQgetResult
来得到结果。
该函数的参数处理和PQdescribePrepared
一样。
PQsendDescribePortal
提交一个请求来获得关于指定入口的信息,但不等待完成。
int PQsendDescribePortal(PGconn *conn, const char *portalName);
这个函数是PQdescribePortal
的一个异步版本:如果它能够发送请求,则返回 1;否则,返回 0。
在一次成功的调用后,调用PQgetResult
来得到结果。
该函数的参数处理和PQdescribePortal
一样。
PQgetResult
等待来自于一个之前的
PQsendQuery
,
PQsendQueryParams
,
PQsendPrepare
,
PQsendQueryPrepared
,
PQsendDescribePrepared
,
PQsendDescribePortal
, 或
PQpipelineSync
调用的结果,并且返回它。
当该命令完成并且没有更多结果时,将返回一个空指针。
PGresult *PQgetResult(PGconn *conn);
PQgetResult
必须被反复调用直到它返回一个空指针,空指针表示该命令完成(如果在没有命令活动时被调用,PQgetResult
将立即返回一个空指针)。
每一个来自PQgetResult
的非空结果应该使用之前描述的同一个PGresult
访问器处理。
不要忘记在处理完之后释放PQclear
的每一个结果对象。注意,只有一个命令是活动的并且PQconsumeInput
还没有读取必要的响应数据时, PQgetResult
将会阻塞。
在管道模式中,否则PQgetResult
将正常返回,除非发生错误;
对于在导致错误的查询之后发送的任何后续查询,直到(不包括)下一个同步点,将返回一个PGRES_PIPELINE_ABORTED
类型的特殊结果,并且在它后面返回一个空指针。
当到达管道同步点时,将返回PGRES_PIPELINE_SYNC
类型的结果。
同步点之后的下一个查询的结果立即跟随(也就是说,在同步点之后没有空指针被返回。)
即使当PQresultStatus
指出一个致命错误时,PQgetResult
也应当被调用直到它返回一个空指针,以允许libpq完全处理该错误信息。
使用PQsendQuery
和PQgetResult
解决了PQexec
的一个问题:如果一个命令字符串包含多个SQL命令,这些命令的结果可以被个别地获得(顺便说一句:这样就允许一种简单的重叠处理形式, 客户端可以处理一个命令的结果,而同时服务器可以继续处理同一命令字符串中后面的查询)。
可以被PQsendQuery
和PQgetResult
获得的另一种常常想要的特性是一次从大型结果中检索一行。这会在第 34.6 节中讨论。
就其本身而言,调用PQgetResult
将仍会导致客户端阻塞,直到服务器完成下一个SQL命令。可以通过正确使用两个函数来避免这种情况:
PQconsumeInput
如果有来自服务器的输入可用,则使用之。
int PQconsumeInput(PGconn *conn);
PQconsumeInput
通常返回 1 表明“没有错误”,而返回 0 表明有某种麻烦发生(此时可以用PQerrorMessage
)。
注意该结果并不表明是否真正收集了任何输入数据。在调用PQconsumeInput
之后,应用可以检查PQisBusy
和/或PQnotifies
来看看它们的状态是否改变。
即使应用还不准备处理一个结果或通知,PQconsumeInput
也可以被调用。
这个函数将读取可用的数 据并且把它保存在一个缓冲区中,从而导致一个select()
的读准备好指示消失。
因此应用可以使用PQconsumeInput
立即清除select()
条件,并且在空闲时再检查结果。
PQisBusy
如果一个命令繁忙则返回 1,也就是说PQgetResult
会阻塞等待输入。返回 0 表示可以调用PQgetResult
而不用担心阻塞。
int PQisBusy(PGconn *conn);
PQisBusy
本身将不会尝试从服务器读取数据,因此必须先调用PQconsumeInput
,否则繁忙状态将永远不会结束。
一个使用这些函数的典型应用将有一个主循环,在主循环中会使用select()
或poll()
等待所有它必须响应的情况。
其中之一将是来自服务器的输入可用,对select()
来说意味着PQsocket
标识的文件描述符上有可读的数据。
当主循环检测到输入准备好时,它将调用PQconsumeInput
读取输入。
然后它可以调用PQisBusy
,如果PQisBusy
返回假(0)则接着调用PQgetResult
。
它还可以调用PQnotifies
检测NOTIFY
消息(见第 34.9 节)。
一个使用PQsendQuery
/PQgetResult
的客户端也可以尝试取消一个正在被服务器处理的命令,见第 34.7 节。
但是,不管PQcancel
的返回值是什么,应用都必须继续使用PQgetResult
进行正常的结果读取序列。一次成功的取消只会导致命令比不取消时更快终止。
通过使用上述函数,我们可以避免在等待来自数据库服务器的输入时被阻塞。不过,在应用发送输出给服务器时还是可能出现阻塞。这种情况比较少见,但是如果发送非常长的 SQL 命令或者数据值时确实可能发生(不过,最有可能是在应用通过COPY IN
发送数据时)。为了避免这种可能性并且实现完全地非阻塞数据库操作,可以使用下列附加函数。
PQsetnonblocking
把连接的状态设置为非阻塞。
int PQsetnonblocking(PGconn *conn, int arg);
如果arg
为 1,把连接状态设置为非阻塞;如果arg
为 0,把连接状态设置为阻塞。如果 OK 返回 0,如果错误返回 -1。
在非阻塞状态,调用
PQsendQuery
, PQputline
,
PQputnbytes
, PQputCopyData
,
和 PQendcopy
将不会阻塞,但是如果它们需要被再次调用则会返回一个错误。
注意PQexec
不会遵循任何非阻塞模式;如果调用PQexec
,那么它的行为总是阻塞的。
PQisnonblocking
返回数据库连接的阻塞状态。
int PQisnonblocking(const PGconn *conn);
如果连接被设置为非阻塞状态,返回 1,如果是阻塞状态返回 0。
PQflush
尝试把任何正在排队的输出数据刷到服务器。如果成功(或者发送队列为空)返回 0, 如果因某种原因失败则返回 -1,或者如果还无法把发送队列中的所有数据都发送出去,则返回 1(这种情况只在连接为非阻塞时候才会发生)。
int PQflush(PGconn *conn);
在一个非阻塞连接上发送任何命令或者数据之后,要调用PQflush
。
如果它返回 1,就要等待套接字变成读准备好或写准备好。如果它变为写准备好,应再次调用PQflush
。
如果它变为读准备好,则应先调用PQconsumeInput
,然后再调用PQflush
。
一直重复直到PQflush
返回 0(有必要检查读准备好并且用PQconsumeInput
耗尽输入,因为服务器可能阻塞给我们发送数据的尝试,例如 NOTICE 消息,并且在我们读它的数据之前它都不会读我们的数据)。
一旦PQflush
返回 0,应等待套接字变成读准备好并且接着按照上文所述读取响应。