对于在普通的、同步的应用程序中提交命令来说,
PQexec
函数已经足够。
不过,它有一些缺陷,
可能对某些用户很重要:
PQexec
会等待命令完成。
应用程序可能有其他工作要做
(例如维护用户界面),
在这种情况下它不会愿意阻塞着等待响应。
由于客户端应用程序的执行在等待结果时被挂起, 应用程序很难决定要尝试取消正在进行的命令。 (可以从信号处理器中做到这一点, 但除此之外没有办法。)
PQexec
只能返回一个
PGresult
结构。
如果提交的命令字符串包含多条
SQL 命令,
除了最后一个
PGresult 之外,
其他的都会被
PQexec
丢弃。
PQexec
总是收集命令的完整结果,
并把它缓冲在单个
PGresult 中。
虽然这简化了应用程序的错误处理逻辑,
但对于包含许多行的结果来说它可能不切实际。
不喜欢这些限制的应用程序可以改用构建
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
释放每个结果对象。
注意,
只有当一条命令处于活动状态且必要的响应数据尚未被
PQconsumeInput
读取时,
PQgetResult
才会阻塞。
在管道模式中,
除非发生错误,
PQgetResult
将正常返回;
对于在引起错误的那个查询之后发送的、
直到(但不包括)下一个同步点的任何后续查询,
都会返回一个类型为
PGRES_PIPELINE_ABORTED
的特殊结果,
并且在它之后会返回一个空指针。
当到达管道同步点时,
会返回一个类型为
PGRES_PIPELINE_SYNC
的结果。
同步点之后的下一个查询的结果会立即跟着出现
(也就是说,
在同步点之后不会返回空指针)。
即便 PQresultStatus
指示一个致命错误,
也应当继续调用
PQgetResult
直到它返回一个空指针,
以让
libpq
完全处理错误信息。
使用 PQsendQuery 和
PQgetResult
解决了 PQexec
的其中一个问题:
如果一条命令字符串包含多条
SQL 命令,
这些命令的结果可以被单独获取。
(顺带一提,
这允许一种简单的重叠处理形式:
在服务器仍在处理同一命令字符串中的后续查询时,
客户端可以正在处理其中一条命令的结果。)
通过
PQsendQuery 和
PQgetResult
可以获得的另一个经常被期望的特性是:
一次只以有限数量的行检索大型查询结果。
这将在
Section 32.6
中讨论。
就其本身而言,
调用 PQgetResult
仍然会导致客户端阻塞,
直到服务器完成下一条
SQL 命令。
通过正确地使用另外两个函数可以避免这种情况:
PQconsumeInput
#如果来自服务器的输入可用, 则消费它。
int PQconsumeInput(PGconn *conn);
PQconsumeInput
通常返回 1 表示
“没有错误”,
但如果出现了某种问题则返回 0
(在这种情况下可以查阅
PQerrorMessage
)。
注意,该结果并不说明是否实际收集到了任何输入数据。
在调用
PQconsumeInput
之后,
应用程序可以检查
PQisBusy
和/或
PQnotifies
来看看它们的状态是否发生了变化。
即使应用程序尚未准备好处理某个结果或通知,
也可以调用
PQconsumeInput
。
该函数会读取可用的数据并把它保存在一个缓冲区中,
从而让 select()
的读就绪指示消失。
因此,
应用程序可以用
PQconsumeInput
立即清除
select()
条件,
然后在闲暇时检查结果。
PQisBusy #
如果一条命令正忙,
即
PQgetResult
将阻塞等待输入,
则返回 1。
返回 0 表示可以放心地调用
PQgetResult
而不会阻塞。
int PQisBusy(PGconn *conn);
PQisBusy
自身不会尝试从服务器读取数据;
因此必须先调用
PQconsumeInput
,
否则忙状态永远不会结束。
使用这些函数的一个典型应用程序会有一个主循环,
它使用 select() 或
poll()
来等待它必须响应的所有条件。
其中一个条件是来自服务器的输入可用,
用 select()
的术语来说,
这意味着由
PQsocket
标识的文件描述符上有可读数据。
当主循环检测到输入就绪时,
它应当调用
PQconsumeInput
来读取输入。
然后它可以调用
PQisBusy,
如果 PQisBusy
返回假(0),
再接着调用
PQgetResult。
它还可以调用
PQnotifies
来检测
NOTIFY
消息(参见
Section 32.9)。
使用
PQsendQuery/PQgetResult
的客户端也可以尝试取消一条仍被服务器处理的命令;
参见 Section 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,
等待套接字变为读就绪,
然后按上述方式读取响应。