PQexec
函数足够在普通、同步应用程序中提交命令。不过,它有几个缺陷,对于有些用户来说很重要
不喜欢这些限制的应用程序可以使用构成 PQexec
的底层函数:PQsendQuery
和 PQgetResult
。还有 PQsendQueryParams
、PQsendPrepare
、PQsendQueryPrepared
、PQsendDescribePrepared
、PQsendDescribePortal
、PQsendClosePrepared
和 PQsendClosePortal
,它们可与 PQgetResult
一起用于复制 PQexecParams
、PQprepare
、PQexecPrepared
、PQdescribePrepared
、PQdescribePortal
PQclosePrepared
和 PQclosePortal
的功能,分别。
PQsendQuery
#向服务器提交一条命令而不等待结果。如果成功分派命令,则返回 1;如果未成功分派命令,则返回 0(在这种情况下,使用 PQerrorMessage
获取有关该失败的更多信息)。
int PQsendQuery(PGconn *conn, const char *command);
在成功调用 PQsendQuery
之后,调用 PQgetResult
一次或多次以获取结果。在 PQgetResult
返回一个空指针(表示命令已完成)之前,不能再次调用 PQsendQuery
(对同一连接)。
在管道模式下,不允许使用该函数。
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
相同。
PQsendClosePrepared
#提交一个在未等待完成的情况下关闭指定已准备语句的请求。
int PQsendClosePrepared(PGconn *conn, const char *stmtName);
这是PQclosePrepared
的异步版本:如果它能够分派请求,则返回 1;如果没有,则返回 0。在成功的调用之后,请调用PQgetResult
以获取结果。该函数的参数处理方式与PQclosePrepared
相同。
PQsendClosePortal
#提交一个在未等待完成的情况下关闭指定门户的请求。
int PQsendClosePortal(PGconn *conn, const char *portalName);
这是PQclosePortal
的异步版本:如果它能够分派请求,则返回 1;如果没有,则返回 0。在成功的调用之后,请调用PQgetResult
以获取结果。该函数的参数处理方式与PQclosePortal
相同。
PQgetResult
#等待来自先前PQsendQuery
、PQsendQueryParams
、PQsendPrepare
、PQsendQueryPrepared
、PQsendDescribePrepared
、PQsendDescribePortal
、PQsendClosePrepared
、PQsendClosePortal
、PQsendPipelineSync
或PQpipelineSync
调用的下一个结果并返回该结果。当命令完成并且不再有更多结果时,返回一个空指针。
PGresult *PQgetResult(PGconn *conn);
PQgetResult
必须反复调用,直到其返回空指针,表明命令已完成。(如果在没有激活命令时进行调用,PQgetResult
将只立即返回一个空指针。)PQgetResult
的每一个非空结果应使用前面已述的相同的 PGresult
访问器函数进行处理。用完后,不要忘记使用 PQclear
释放每个结果对象。请注意,PQgetResult
仅当一个命令已激活且必要的响应数据尚未被 PQconsumeInput
读取时才会阻塞。
在管道模式中,PQgetResult
将正常返回值,除非发生错误;对于在导致错误的查询之后发送的任何后续查询,直至下一个同步点(不包括下一个同步点),将返回类型为 PGRES_PIPELINE_ABORTED
的特殊结果,紧随其后将返回空指针。当达到管道同步点时,将返回类型为 PGRES_PIPELINE_SYNC
的结果。下一个查询的结果紧随同步点之后(即,同步点之后不会返回空指针)。
即使 PQresultStatus
表明存在致命错误,也应调用 PQgetResult
,直到其返回空指针,以允许 libpq 完全处理错误信息。
使用 PQsendQuery
和 PQgetResult
可以解决 PQexec
的一个问题:如果一个命令字符串包含多个SQL命令,则可以分别获得这些命令的结果。(顺便说一下,这允许多种形式的重叠处理:当服务器仍在处理同一命令字符串中的后续查询时,客户端可以处理一个命令的结果。)
另一个经常需要通过 PQsendQuery
和 PQgetResult
实现的功能是可以一次最多检索限量行大查询结果。这在第 32.6 节中讨论。
单独调用 PQgetResult
仍将导致客户端阻塞,直到服务器完成下一个SQL命令。这可以通过适当使用另外两个函数来避免
PQconsumeInput
#如果可从服务器获得输入,则使用它。
int PQconsumeInput(PGconn *conn);
PQconsumeInput
通常返回 1 表示 “无错误”,但如果出现某种麻烦(在这种情况下,可以查阅 PQerrorMessage
),则返回 0。请注意,此结果不会提示是否实际收集到任何输入数据。调用 PQconsumeInput
后,应用程序可以检查 PQisBusy
和/或 PQnotifies
以查看其状态是否已更改。
即使应用程序尚未准备好处理结果或通知,也可以调用 PQconsumeInput
。此函数将读取可用数据并将其保存在缓冲区中,从而导致消除 select()
已读就绪指示。因此应用程序可以使用 PQconsumeInput
立即清除 select()
条件,然后从容检查结果。
PQisBusy
#如果某个命令正忙,即 PQgetResult
将因等待输入而被阻塞,则返回 1。返回 0 表示可以保证调用 PQgetResult
时不进行阻塞。
int PQisBusy(PGconn *conn);
PQisBusy
本身不会尝试从服务器读取数据;因此,必须首先调用 PQconsumeInput
,否则忙状态将永远不会结束。
使用这些功能的典型应用程序将具有一个主循环,该主循环使用 select()
或 poll()
来等待必应响应的所有条件。其中一个条件是服务器中可获得的输入,根据 select()
的规定,这意味着由 PQsocket
识别的文件描述符上的可读数据。当主循环检测到输入就绪时,它应调用 PQconsumeInput
来读取输入。然后,它可以调用 PQisBusy
,随后调用 PQgetResult
,如果 PQisBusy
返回假 (0) 的话。它还可以调用 PQnotifies
来检测 NOTIFY
消息(请参见 第 32.9 节)。
使用 PQsendQuery
/PQgetResult
的客户端还可以尝试取消服务器仍在处理的命令;请参见 第 32.7 节。但是无论 PQcancelBlocking
的返回值如何,应用程序都必须使用 PQgetResult
继续进行正常的读取结果序列。成功的取消只会导致命令终止得比否则更快。
通过使用上述函数,可以避免在等待数据库服务器输入时阻塞。但是,应用程序仍然可能会阻塞,等待向服务器发送输出。这种情况相对不常见,但如果发送非常长的 SQL 命令或数据值,则可能会发生这种情况。(但是,如果应用程序通过 COPY IN
发送数据,则这种可能性更大。)要防止出现这种情况并实现完全非阻塞的数据库操作,可以使用以下附加函数。
PQsetnonblocking
#设置连接的非阻塞状态。
int PQsetnonblocking(PGconn *conn, int arg);
如果 arg
为 1,则将连接状态设置为非阻塞;如果 arg
为 0,则将其设置为阻塞。如果正确,则返回 0;如果出错,则返回 -1。
在非阻塞状态下,对 PQsendQuery
、PQputline
、PQputnbytes
、PQputCopyData
和 PQendcopy
的成功调用不会阻塞;其更改会存储在本地输出缓冲区中,直至刷新为止。不成功的调用将返回错误,并且必须重试。
请注意,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,请等待套接字变为可读,然后按照上述说明读取响应。