PostgreSQL教程(三十七):客户端接口(一)之 libpq - C 库

一、数据库连接控制函数

下面的函数处理与PostgreSQL服务器联接的事情。 一个应用程序一次可以与多个服务器建立联接。 (这么做的原因之一是访问多于一个数据库。) 每个连接都是用一个从函数 PQconnectdbPQconnectdbParamsPQsetdbLogin 获得的PGconn对象表示。 注意,这些函数总是返回一个非空的对象指针,除非存储器少得连个 PGconn对象都分配不出来。在把查询发送给连接对象之前, 可以调用PQstatus函数来检查一下返回值看看连接是否成功。

警告:

在Unix上,用打开的libpq连接分支化一个过程会导致不可预知的结果, 因为父进程和子进程共享同一个套接字和操作系统资源。因为这个原因, 不建议这样的使用,尽管从子进程中执行exec 加载一个新的可执行文件是安全的。

注意:

在Windows上,如果一个数据库连接重复的启动和关闭,有一个方式提高性能。 内部的,libpq为连接启动和关闭分别调用WSAStartup()WSACleanup()。 WSAStartup()增加一个内部Windows库引用计数,而WSACleanup() 减少一个。当引用计数是一时,调用WSACleanup()释放所有资源和所有DLL是空载的。 这是一个昂贵的操作。为了避免它,一个应用可以手动调用WSACleanup(), 这样在最后一个数据库连接关闭时,资源将不会被释放。

PQconnectdbParams

        与数据库服务器建立一个新的连接。

PGconn *PQconnectdbParams(const char * const *keywords,
                          const char * const *values,
                          int expand_dbname);

        这个函数用从两个NULL结束的数组中来的参数打开一个新的数据库连接。 第一个,keywords,定义为一个字符串的数组,每个都成为一个关键字。 第二个,values,给每个关键字一个值。与下面的PQsetdbLogin 不同的是,我们可以不必更换函数签名(名字)就可以扩展参数集, 所以我们建议应用程序中使用这个函数(或者它的类似的非阻塞变种 PQconnectStartParamsPQconnectPoll)。

        目前公认的参数关键字在下面1.2节中列出。

        当expand_dbname是非零的时,允许将dbname 的关键字值看做一个连接字符串。可能的格式的更详细信息在下面1.1节 中显示。

        传入的参数可以为空,表明使用所有缺省的参数,或者可以包含一个或更多个参数设置。 它们的长度应该匹配。处理将会在keywords数组的最后一个非 NULL元素停止。

        如果没有指定任何参数,则使用对应的环境变量(参阅第十四节)。 如果环境变量也没有设置,则使用表示的内建缺省。

        通常,关键字是从这些数组的开始以索引的顺序处理的。这样的影响是,当关键字重复时, 获得最后处理的值。因此,通过小心的放置dbname关键字, 有可能决定哪个被conninfo字符串覆盖,哪个不被覆盖。

PQconnectdb

        与数据库服务器建立一个新的连接。

PGconn *PQconnectdb(const char *conninfo);

        这个函数用从一个字符串conninfo来的参数与数据库打开一个新的联接。

        传入的参数可以为空,表明使用所有缺省的参数,或者可以包含一个或更多个用空白间隔的参数设置, 或者它可以包含一个URI。参阅下面1.1节获取细节。

PQsetdbLogin

        与数据库服务器建立一个新的连接。

PGconn *PQsetdbLogin(const char *pghost,
                     const char *pgport,
                     const char *pgoptions,
                     const char *pgtty,
                     const char *dbName,
                     const char *login,
                     const char *pwd);

        这个函数是PQconnectdb前身,它有固定个数的参数。它有相同的功能, 只是在调用中那些它缺少的参数总是用缺省值。如果要给任意的固定参数设置缺省值, 那么写一个NULL或者一个空字串给它们。

        如果dbName包含一个=符或者有一个有效的连接 URI前缀,它被看做一个conninfo字符串, 就和它已经被传递到PQconnectdb中完全一样, 然后剩余的参数就像为PQconnectdbParams指定的那样应用。

PQsetdb

        与数据库服务器建立一个新的连接。

PGconn *PQsetdb(char *pghost,
                char *pgport,
                char *pgoptions,
                char *pgtty,
                char *dbName);

        这是一个调用PQsetdbLogin的宏,只是login和pwd 参数是空指针。提供这个函数是为了与非常老版本的程序兼容。

PQconnectStartParams
PQconnectStart
PQconnectPoll

        与数据库服务器建立一次非阻塞的联接

PGconn *PQconnectStartParams(const char * const *keywords,
                             const char * const *values,
                             int expand_dbname);

PGconn *PQconnectStart(const char *conninfo);

PostgresPollingStatusType PQconnectPoll(PGconn *conn);

        这三个函数用于打开一个与数据库服务器之间的非阻塞的联接你的应用的执行线程在执行它的时候不会因远端的 I/O 而阻塞。 这个方法的要点是等待 I/O 结束可以发生在应用的主循环里, 而不是在PQconnectdbParamsPQconnectdb里, 这样应用可以把这件事与其它操作并发起来一起执行。

        对于PQconnectStartParams,数据库联接是用从keywords 和values数组中取得的参数进行的,并且是使用expand_dbname 控制的,就像上面PQconnectdbParams里描述的一样。

        对于PQconnectStart,数据库联接是用从conninfo 字符串里取得的参数进行的,这个字符串的格式与上面PQconnectdb 里描述的一样。

  PQconnectStartParamsPQconnectStart 和PQconnectPoll都不会阻塞(进程),不过有一些条件:

  • 必须正确提供hostaddr和host参数以确保不会发生正向或者反向的名字查找。 参阅下面1.2节里的这些参数的文档获取细节。

  • 如果你调用了PQtrace,确保你跟踪进入的流对象不会阻塞。

  • 你必须在调用PQconnectPoll之前确保 socket 处于正确的状态, 像下面描述的那样。

        注意:PQconnectStartParams的使用类似于下面显示的PQconnectStart

        要开始一次非阻塞连接请求,调用conn = PQconnectStart("connection_info_string")。 如果conn是空,表明libpq无法分配一个新的PGconn结构。 否则,返回一个有效的PGconn指针(尽管还不一定代表一个与数据库有效联接)。 PQconnectStart一返回,调用status = PQstatus(conn)。 如果status等于CONNECTION_BAD,PQconnectStart失败。

        如果PQconnectStart成功了,下一个阶段是轮询libpq, 这样它就可以继续连接序列动作。使用PQsocket(conn) 获取数据库链接下层的套接字描述符。像这样循环:如果PQconnectPoll(conn) 的最后一个返回是PGRES_POLLING_READING,那么就等到套接字准备好被读取了的时候 (就像系统函数select()poll(),或者类似的系统调用声明的那样)。 然后再次调用PQconnectPoll(conn)。反过来,如果PQconnectPoll(conn) 最后返回PGRES_POLLING_WRITING,那么就等到套接字准备好可以写了, 然后再次调用PQconnectPoll(conn)。如果你还没调用PQconnectPoll, 比如,刚刚调用完PQconnectStart,那么按照它刚返回PGRES_POLLING_WRITING 的原则行动。继续这个循环直到PQconnectPoll(conn)返回PGRES_POLLING_FAILED, 表明连接过程失败,或者PGRES_POLLING_OK,表明连接成功建立。

        在连接的任意时刻,我们都可以通过调用PQstatus来检查联接的状态。 如果这是CONNECTION_BAD,那么联接过程失败;如果是CONNECTION_OK, 那么联接已经做好。这两种状态同样也可以从上面的PQconnectPoll的返回值里检测到。 其他状态可能(也只能)在一次异步联接过程中发生。这些标识连接过程的当前状态, 因而可能对给用户提供反馈有帮助。这些状态可能包括:

        CONNECTION_STARTED

                等待进行连接。

        CONNECTION_MADE

                连接成功;等待发送。

        CONNECTION_AWAITING_RESPONSE

                等待来自服务器的响应。

        CONNECTION_AUTH_OK

                已收到认证;等待后端启动结束。

        CONNECTION_SSL_STARTUP

                协商 SSL 加密。

        CONNECTION_SETENV

                协商环境驱动的参数设置。

        注意,尽管这些常量将保持下去(为了维持兼容性),应用决不应该依赖于这些常量以某种特定顺序出现, 或者是根本不应依赖于这些常量,或者是不应该依赖于这些状态总是某个文档声明的值。 一个应用可能像下面这样:

switch(PQstatus(conn))
{
        case CONNECTION_STARTED:
            feedback = "Connecting...";
            break;

        case CONNECTION_MADE:
            feedback = "Connected to server...";
            break;
.
.
.
        default:
            feedback = "Connecting...";
}

在使用PQconnectPoll的时候,连接参数connect_timeout 将被忽略;判断是否超时是应用的责任。否则,后面跟着一个PQconnectPoll 循环的PQconnectStart等效于PQconnectdb

要注意如果PQconnectStart返回一个非空的指针,你必须在使用完它(指针) 之后调用PQfinish,以处理那些结构和所有相关的存储块。 甚至是在连接尝试失败或放弃时也要这样处理。

PQconndefaults

        返回缺省的联接选项。

PQconninfoOption *PQconndefaults(void);

typedef struct
{
    char   *keyword;   /* 选项的键字 */
    char   *envvar;    /* 退守的环境变量名 */
    char   *compiled;  /*  退守的编译时缺省值*/
    char   *val;       /* 选项的当前值,或者 NULL */
    char   *label;     /* 连接对话里字段的标识 */
    char   *dispchar;  /* 在连接对话里为此字段显示的字符。
                          数值有:
                          ""        原样现实输入的数值
                          "*"       口令字段 - 隐藏数值
                          "D"       调试选项 - 缺省的时候不显示 */
    int     dispsize;  /*  对话中字段的以字符计的大小 */
} PQconninfoOption;

        返回一个连接选项数组。可以用于获取所有可能的PQconnectdb 选项和它们的当前缺省值。返回值指向一个PQconninfoOption 结构的数组,该数组以一个有 NULL keyword指针的条目结束。 如果无法分配内存,则返回空指针。注意当前缺省值(val域) 将依赖于环境变量和其他环境。调用者必须把连接选项当作只读对待。

        在处理完选项数组后,把数组交给PQconninfoFree释放。 如果没有这么做,每次调用PQconndefaults都会有一小部分内存泄漏。

PQconninfo

        返回活的连接使用的连接选项。

PQconninfoOption *PQconninfo(PGconn *conn);

        返回一个连接选项数组。可以用于获取所有可能的PQconnectdb 选项和用于连接到服务器的值。返回值指向一个PQconninfoOption 结构的数组,该数组以一个有 NULL keyword指针的条目结束。 以上所有PQconndefaults的注意事项也应用到PQconninfo 的结果。

PQconninfoParse

        从提供的连接字符串中返回解析的连接选项。

PQconninfoOption *PQconninfoParse(const char *conninfo, char **errmsg);

        解析连接字符串并作为数组返回结果选项;或者如果连接字符串有问题返回NULL。 这个函数可以用来在提供的连接字符串中提取PQconnectdb选项。 返回值指向一个PQconninfoOption结构的数组, 该数组以一个有 NULL keyword指针的条目结束。

        所有合法选项将在结果数组中显示,但是PQconninfoOption 的任何没有在连接字符串中出现的选项将把val设置为NULL; 缺省值不插入。

        如果errmsg是非NULL的,那么*errmsg在成功时设置为NULL, 否则是malloc的解释问题的错误字符串。(*errmsg设置为NULL 并且函数返回NULL是可能的;这表示一个内存溢出条件。)

        在处理完选项数组后,把数组交给PQconninfoFree释放。 如果没有这么做,每次调用PQconndefaults都会有一小部分内存泄漏。 相反的,如果错误发生了并且errmsg非NULL, 确保使用PQfreemem释放错误字符串。

PQfinish

        关闭与服务器的连接。同时释放被PGconn对象使用的存储器。

void PQfinish(PGconn *conn);

        注意,即使与服务器的连接尝试失败(可由PQstatus判断), 应用也要调用PQfinish释放被PGconn 对象使用的存储器。不应该在调用PQfinish后再使用PGconn指针。

PQreset

        重置与服务器的通讯端口。

void PQreset(PGconn *conn);

        此函数将关闭与服务器的连接并且试图与同一个服务器重建新的连接, 使用所有前面使用过的参数。这在失去工作连接后进行故障恢复时很有用。

PQresetStart
PQresetPoll

        以非阻塞模式重置与服务器的通讯端口。

int PQresetStart(PGconn *conn);

PostgresPollingStatusType PQresetPoll(PGconn *conn);

        此函数将关闭与服务器的连接并且试图与同一个服务器重建新的连接,使用所有前面使用过的参数。 这在失去工作连接后进行故障恢复时很有用。它们和上面的PQreset 的区别是它们工作在非阻塞模式。这些函数的使用有与上面PQconnectStartParams、 PQconnectStartPQconnectPoll一样的限制。

        要发起一次连接重置,调用PQresetStart。如果它返回 0, 那么重置失败。如果返回 1,用与使用PQresetPoll 建立连接的同样的方法使用PQresetPoll重置连接。

PQpingParams

  PQpingParams报告服务器的状态。它接受和PQconnectdbParams 一样的连接参数,在下面描述。不需要应用正确的用户名、密码或数据库名的值获取服务器状态; 不过,如果提供了不正确的值,服务器将记录一次失败的连接尝试。

PGPing PQpingParams(const char * const *keywords,
                    const char * const *values,
                    int expand_dbname);

        该函数返回下列的值之一:

        PQPING_OK

                服务器正在运行并且似乎接受了连接。

        PQPING_REJECT

                服务器正在运行,但是在一个不允许连接的状态(启动、关闭或崩溃恢复)。

        PQPING_NO_RESPONSE

                联系不上服务器。这可能表明服务器没有运行,或者给出的连接参数有什么错误 (例如,错误的端口号),或者网络连接有问题(例如,防火墙阻塞连接请求)。

        PQPING_NO_ATTEMPT

                没有尝试连接到服务器,因为提供的参数明显的不正确或者有一些客户端侧的问题(例如,内存溢出)。

PQping

  PQping报告服务器的状态。它接受和PQconnectdb 一样的连接参数,在下面描述。不需要应用正确的用户名、密码或数据库名的值获取服务器状态; 不过,如果提供了不正确的值,服务器将记录一次失败的连接尝试。

PGPing PQping(const char *conninfo);

        返回值和PQpingParams的相同。

1.1 连接字符串

几个libpq函数分析用户指定的字符串以获取连接参数。 这些字符串有两个可接受的格式:纯keyword = value字符串和 RFC 3986 URIs。

1.1.1 关键字/值连接字符串

在第一中格式中,每个参数以keyword = value的形式设置。 等号周围的空白是可选的。要写一个空值或者一个包含空白的值,你可以用一对单引号包围它们, 例如,keyword = 'a value'。数值内部的单引号和反斜杠必须用一个反斜杠转义, 比如,\'或\\。

示例:

host=localhost port=5432 dbname=mydb connect_timeout=10

可识别的参数关键字在第1.2节中列出。

1.1.2 连接URI

连接URI的通用格式是:

postgresql://[user[:password]@][netloc][:port][/dbname][?param1=value1&...]

URI模式标识符可以是postgresql://或 postgres://。URI的每个部分都是可选的。 下列示例举例说明了有效的URI语法使用:

postgresql://
postgresql://localhost
postgresql://localhost:5433
postgresql://localhost/mydb
postgresql://user@localhost
postgresql://user:secret@localhost
postgresql://other@localhost/otherdb?connect_timeout=10&application_name=myapp

URI的层次部分的组件也可以作为参数给出。例如:

postgresql:///mydb?host=localhost&port=5433

百分号可以用在URI的任何部分来包含特殊含义的符号。

忽略任何不对应于在第1.2节列出的关键字的连接参数, 并将关于它们的警告消息发送到stderr。

为了提高JDBC连接URI的兼容性,参数ssl=true 的实例被翻译成sslmode=require。

主机部分是主机名或者IP地址要指定一个IPv6主机地址,将它包含在方括号中

postgresql://[2001:db8::1234]/database

主机部分解释为参数host的描述。特别的, 如果主机部分为空或者以斜线开头,那么选择一个Unix域套接字连接,否则初始化一个TCP/IP连接。 不过要注意,斜线是URI分层部分的一个保留字符。所以,要指定一个非标准Unix域套接字路径, 要么在URI中省略主机声明并指定主机为一个参数,要么在URI的主机部分添加百分号:

postgresql:///dbname?host=/var/lib/postgresql
postgresql://%2Fvar%2Flib%2Fpostgresql/dbname

1.2 关键字参数

目前可识别的参数键字是:

host

        要联接的主机名。如果主机名以斜杠开头, 则它声明使用 Unix 域套接字通讯而不是 TCP/IP 通讯;该值就是套接字文件所存储的目录。 如果没有声明host,那么缺省时是与位于/tmp目录 (或者制作PostgreSQL的时候声明的套接字目录)里面的 Unix-域套接字连接。 在没有 Unix 域套接字的机器上,缺省是与localhost连接。

hostaddr

        与之连接的主机的 IP 地址。这个应该是标准的IPv4 地址格式,比如,172.28.40.9。 如果你的机器支持 IPv6,那么你也可以使用 IPv6 的地址。如果声明了一个非空的字符串, 那么使用 TCP/IP 通讯机制。

        使用hostaddr取代host可以让应用避免一次主机名查找, 这一点对于那些有时间约束的应用来说可能是非常重要的。不过,Kerberos、GSSAPI 或SSPI认证方法和verify-full SSL证书验证要求主机(host)名。 因此,应用下面的规则:

  • 如果声明了不带hostaddr的host那么就强制进行主机名查找。

  • 如果声明中没有host,hostaddr的值给出服务器网络地址; 如果认证方法要求主机名,那么连接尝试将失败。

  • 如果同时声明了host和hostaddr,那么hostaddr 的值作为服务器网络地址。host的值将被忽略,除非认证方法需要它, 在这种情况下它将被用作主机名。

        要注意如果host不是网络地址hostaddr处的服务器名, 那么认证很有可能失败。同样,在~/.pgpass(参阅第十五节) 中是使用host而不是hostaddr来标识连接。

        如果主机名(host)和主机地址都没有,那么libpq 将使用一个本地的 Unix 域套接字进行连接;或者是在没有 Unix 域套接字的机器上, 它将尝试与localhost连接。

port

        主机服务器的端口号,或者在 Unix 域套接字联接时的套接字扩展文件名。

dbname

        数据库名。缺省和用户名相同。在某些情况下,为扩展的格式检查值; 参阅第 31.1.1 节获取更多信息。

user

        要连接的PostgreSQL用户名。 缺省是与运行该应用的用户操作系统名同名的用户。

password

        如果服务器要求口令认证,所用的口令。

connect_timeout

        连接的最大等待时间,以秒计(用十进制整数字串书写)。零或者不声明表示无穷。 我们不建议把连接超时的值设置得小于 2 秒。

client_encoding

        为这个连接设置client_encoding配置参数。 除了对应的服务器选项接受的值,你可以使用auto 从客户端中的当前环境中确定正确的编码(Unix系统上是 LC_CTYPE环境变量)。

options

        添加命令行选项以在运行时发送到服务器。例如,设置为-c geqo=off 设置geqo参数的会话的值为off。

application_name

        为application_name配置参数指定一个值。

fallback_application_name

        为application_nameapplication_nameapplication_name配置参数指定一个回退值。 如果没有通过连接参数或PGAPPNAME环境变量给定 application_name值,那么将使用这个值。 在想要设置缺省应用名但是允许用户重写的通用实用程序中指定一个回退名是有用的。

keepalives

        控制客户端侧的TCP保持激活是否使用。缺省值是1,意思为打开,但是如果不想要保持激活, 你可以更改为0,意思为关闭。通过Unix域套接字做的连接忽略这个参数。

keepalives_idle

        在TCP应该发送一个保持激活的信息给服务器之后,控制不活动的秒数。 0值表示使用系统缺省。通过Unix域套接字做的连接或者如果禁用了保持激活则忽略这个参数。 只有在TCP_KEEPIDLE和TCP_KEEPALIVE套接字选项可用的系统上支持这个参数, 在Windows上还是在其他系统上是没什么影响的。

keepalives_interval

        在TCP保持激活信息没有被应该传播的服务器承认之后,控制秒数。0值表示使用系统缺省。 通过Unix域套接字做的连接或者如果禁用了保持激活则忽略这个参数。 只有在TCP_KEEPINTVL套接字选项可用的系统上支持这个参数, 在Windows上还是在其他系统上是没什么影响的。

keepalives_count

        在认为客户端到服务器的连接死亡之前,控制可以丢失的TCP保持激活的数量。0值表示使用系统缺省。 通过Unix域套接字做的连接或者如果禁用了保持激活则忽略这个参数。 只有在TCP_KEEPINTVL套接字选项可用的系统上支持这个参数, 在Windows上还是在其他系统上是没什么影响的。

tty

        忽略(以前,这个选项声明服务器日志的输出方向)。

sslmode

        这个选项决定是否需要和服务器协商一个SSL TCP/IP连接, 以及以什么样的安全优先级与服务器进行SSL TCP/IP连接。 这里有六个模式:

disable

        只进行一个非SSL连接

allow

        首先尝试一个非SSL连接;如果失败,尝试一个SSL连接

prefer (default)

        首先尝试SSL连接;如果失败,尝试一个非SSL连接

require

        尝试一个SSL连接。如果有根CA文件,则按照指定了verify-ca 的相同方式验证该证书

verify-ca

        只尝试一个SSL连接,并核实服务器证书是由一个受信任的认证中心(CA)发布的

verify-full

        只尝试一个SSL连接,核实服务器证书是由受信任的CA发布的, 并且该服务器主机名匹配证书中的服务器主机名。

        参阅第十八节获取这些选项工作的详细描述。

        Unix域套接字通信忽略sslmode。如果PostgreSQL 编译时没有打开 SSL 支持,那么使用选项require、verify-ca或 verify-full将导致一个错误,而选项allow和prefer 将被接受,但是libpq实际上不会企图进行SSL连接。

requiressl

        这个选项因为有了sslmode设置之后已经废弃了。

        如果设为1,则要求与服务器进行SSL联接(等效于sslmode require)。如果服务器不支持SSL,那么libpq 将马上拒绝联接。设置为0(缺省),与服务器进行协商连接类型(等效于sslmode prefer)。这个选项只有在编译PostgreSQL时打开了 SSL 支持才有效。

sslcompression

        如果设置为1(缺省),通过SSL连接进行的数据发送将被压缩(这要求OpenSSL 版本0.9.8或更高)。如果设置为0,将禁用压缩(这需要OpenSSL 1.0.0或更高)。 如果连接没有通过SSL进行,或者如果使用的OpenSSL版本不支持它,则忽略该参数。

        压缩使用CPU时间,但是如果网络是瓶颈,那么可以提高吞吐量。 如果CPU性能是限制因素,那么禁用压缩可以提高响应时间和吞吐量。

sslcert

        这个参数指定客户端SSL认证的文件名,替换缺省的~/.postgresql/postgresql.crt。 如果没有做SSL连接,则忽略这个参数。

sslkey

        这个参数指定客户端使用的秘钥的位置。也可以指定一个用来替换缺省 ~/.postgresql/postgresql.key的文件名,或者指定一个从外部 "引擎"获取的键(引擎是OpenSSL可加载模块)。 一个外部引擎声明应该包括一个由冒号分隔的引擎名字和特定于引擎的键标识符。 如果没有做SSL连接则忽略这个参数。

sslrootcert

        这个参数声明一个包含SSL认证授权(CA)证书的文件名。 如果该文件存在,那么将要验证的服务器的证书将由这些授权之一签署。 缺省是~/.postgresql/root.crt。

sslcrl

        这个参数声明SSL证书撤销列表(CRL)的文件名。在这个文件中列出的证书, 如果该文件存在,将在尝试认证服务器的证书时被拒绝。缺省是 ~/.postgresql/root.crl。

requirepeer

        这个参数声明服务器的操作系统用户名,例如requirepeer=postgres。 当制作一个Unix域套接字连接时,如果设置了该参数,那么在连接的开始, 客户端检查服务器进程是否运行在指定的用户名之下;如果不是,则连接带有错误退出。 这个参数可以用来提供服务器认证,类似于在TCP/IP连接上可用SSL证书。 (请注意,如果Unix域套接字在/tmp中或另一个公开可写位置, 那么任意用户都可以在这里启动一个服务器监听。 使用这个参数确保你连接到一个受信任的用户运行的服务器。) 这个选项只有在实现了peer认证方法的平台上支持;

krbsrvname

        使用Kerberos 5或GSSAPI认证时使用的Kerberos服务名。 这个名字必须和服务器给Kerberos认证配置的服务名相同,才能认证成功。 

gsslib

        为GSSAPI认证使用的GSS库。只在Windows上使用。设置为gssapi 强迫libpq为认证使用GSSAPI库而不是缺省的SSPI。

service

        用于额外参数的服务名。它在pg_service.conf里面声明一个服务名, 这个配置文件保存额外的连接参数。这样就允许应用只声明一个服务名, 而连接参数就可以在一个地方维护了。参阅第十六节

二、连接状态函数

这些函数可以用于询问现存数据库连接对象的状态。

提示:
libpq应用程序员应该仔细维护PGconn结构。 使用下面的访问函数来获取PGconn的内容。不建议使用libpq-int.h 引用PGconn内部的字段,因为这些字段在今后可能被改变。

下面的函数返回连接建立时的参数值。这些参数在PGconn对象的生命期期间是固定的。

PQdb

        返回连接的数据库名。

char *PQdb(const PGconn *conn);

PQuser

        返回连接的用户名。

char *PQuser(const PGconn *conn);

PQpass

        返回连接的口令。

char *PQpass(const PGconn *conn);

PQhost

        返回连接的服务器主机名。

char *PQhost(const PGconn *conn);

PQport

        返回连接的端口号。

char *PQport(const PGconn *conn);

PQtty

        返回连接的调试控制台TTY。(这个已经过时了, 因为服务器不再注意TTY设置,这个函数的存在是为了向下兼容。)

char *PQtty(const PGconn *conn);

PQoptions

        返回连接请求中传递的命令行选项。

char *PQoptions(const PGconn *conn);

下面的函数返回那些在对PGconn对象进行操作的过程中可能变化的状态数据。

PQstatus

        返回连接的状态。

ConnStatusType PQstatus(const PGconn *conn);

        这个状态可以是一系列值之一。不过,我们在一个异步连接过程外面只能看到其中的两个: CONNECTION_OK和CONNECTION_BAD。 成功连接到数据库返回状态CONNECTION_OK。 失败的连接尝试用状态CONNECTION_BAD标识。通常, 一个OK状态将保持到PQfinish,但是一个通讯失败可能会导致状态过早的改变为 CONNECTION_BAD。这时应用可以试着调用PQreset来恢复。

        参阅PQconnectStartParamsPQconnectStartPQconnectPoll 条目看看可能出现的其他状态码。

PQtransactionStatus

        返回服务器的当前事务内状态。

PGTransactionStatusType PQtransactionStatus(const PGconn *conn);

        状态可以是PQTRANS_IDLE(当前空闲),PQTRANS_ACTIVE (正在处理一个命令),PQTRANS_INTRANS(空闲,在一个合法的事务块内), 或者PQTRANS_INERROR(空闲,在一个失败的事务块内)。如果连接有问题, 则返回PQTRANS_UNKNOWN。只有在一个查询发送给了服务器并且还没有完成的时候才返回 PQTRANS_ACTIVE。

小心:

当使用参数autocommit设置为关闭的PostgreSQL 7.3服务器时, PQtransactionStatus将给出不正确的结果。服务器端自动提交特性已经废弃了, 并且在后来的服务器版本中不再存在。

PQparameterStatus

        查找服务器的一个当前参数设置。

const char *PQparameterStatus(const PGconn *conn, const char *paramName);

        有些参数值在建立连接或者它们的值改变的时候会由服务器自动报告。 PQparameterStatus可以用查询这些设置。如果参数已知, 那么它返回当前值,否则返回NULL。

        当前版本报告的参数有server_version,server_encoding, client_encoding,application_name,is_superuser, session_authorization,DateStyle,IntervalStyle, TimeZone,integer_datetimes和standard_conforming_strings。 (8.0之前的版本不报告server_encoding,TimeZone和 integer_datetimes;8.1之前的版本不报告standard_conforming_strings; 8.4之前的版本不报告IntervalStyle;9.0之前的版本不报告application_name。) 请注意server_version,server_encoding和integer_datetimes 不能再启动后修改。

        协议版本3.0之前的服务器不会报告参数设置,但是libpq 里包含一些逻辑用于获取server_version和client_encoding的数值。 我们鼓励应用里面使用PQparameterStatus,而不是使用ad hoc 代码来检测这些值。(不过要注意,在3.0之前的连接协议里,启动后通过SET 改变了client_encoding将不会被PQparameterStatus反映出来。) 对于server_version,又见PQserverVersion, 它返回数值形式,更容易进行比较。

         如果没有为standard_conforming_strings报告数值,应用可以假设它是off, 也就是说,在字符串文本里,把反斜杠当做转义。同样,如果出现了这个参数, 就可以当作一个指示,表示接受转义字符串(E'...')的语法。

        尽管返回的指针声明为const,它实际上指向一个和PGconn 结构关联的可变存储区。因此假设这个指针跨查询保持有效是不明智的。

PQprotocolVersion

        查询使用的前/后端协议。

int PQprotocolVersion(const PGconn *conn);

        应用可能希望使用这个函数来判断某些特性是否被支持。目前,可能的数值是2 (2.0协议),3(3.0协议)或0(连接错误)。在连接启动完成之后,这个数值将不会改变, 但是在连接重置的过程中,理论上是可能改变的。在与PostgreSQL 7.4 或更高版本沟通时,通常使用3.0协议;7.4以前的服务器只支持协议2.0。 (协议1.0过时了,不被libpq支持。)

PQserverVersion

        返回一个整数,代表后端版本。

int PQserverVersion(const PGconn *conn);

        应用可以使用这个函数判断它们连接的数据库服务器的版本。数字是通过把主、 次及版本号转换成两位十进制数并且把它们连接在一起组成的。例如, 版本8.1.5将被返回80105,版本8.2将被返回80200(前导零没有显示)。 如果连接失败,则返回零。

PQerrorMessage

        返回连接中操作产生的最近的错误消息。

char *PQerrorMessage(const PGconn *conn);

        几乎所有libpq函数在失败时都会为PQerrorMessage 设置一个信息。注意,libpq的传统是,一个非空的 PQerrorMessage结果会由多行组成,并且将包含一个结尾的新行。 调用者不应该直接释放结果。结果的释放是在将PGconn句柄传递给 PQfinish的时候自动进行的。我们不能假设在不同的PGconn 结构操作中,结果字串都是一样的。

PQsocket

        获取与服务器连接的套接字的文件描述符编号。一个有效的描述符应该是大于或等于0; 结果为-1表示当前没有与服务器的连接打开。(在正常的操作中,这个结果不会改变, 但是可能在连接启动或者重置的过程中变化。)

int PQsocket(const PGconn *conn);

PQbackendPID

        返回后端进程处理此连接的进程号ID (PID)

int PQbackendPID(const PGconn *conn);

        这个后端PID在调试和对比NOTIFY 信息(包括发出通知的后端进程的PID)时很有用。 注意该PID属于运行数据库服务器主机的进程, 而不是本地主机!

PQconnectionNeedsPassword

        如果连接的认证方法需要一个密码则返回true (1),但是没有可用的。 如果没有则返回false (0)。

int PQconnectionNeedsPassword(const PGconn *conn);

        此功能可用于连接尝试失败后决定是否提示用户输入密码。

PQconnectionUsedPassword

        如果连接的认证方法使用密码则返回true (1)。否则返回false (0)。

int PQconnectionUsedPassword(const PGconn *conn);

        此功能可应用于失败或成功连接后尝试检测服务器是否要求密码。

PQgetssl

        返回连接中使用的SSL结构,或者没有使用SSL则返回null。

void *PQgetssl(const PGconn *conn);

        这个结构可以用于核实加密级别,检查服务器认证等信息。参考OpenSSL 文档获取关于这个结构的更多信息。

        实际返回值的类型是SSL *,而SSL的类型是由 OpenSSL库定义的,但是没有用这种方式声明, 以避免请求OpenSSL头文件。要使用这个函数, 可以使用下面的代码行:

#include <libpq-fe.h>
#include <openssl/ssl.h>

...

    SSL *ssl;

    dbconn = PQconnectdb(...);
    ...

    ssl = PQgetssl(dbconn);
    if (ssl)
    {
        /* use OpenSSL functions to access ssl */
    }

 三、命令执行函数

一旦与数据库服务器的连接成功建立,便可以使用这里描述的函数执行SQL查询和命令。

3.1 主函数

PQexec

        给服务器提交一条命令并且等待结果。

PGresult *PQexec(PGconn *conn, const char *command);

        返回一个PGresult指针或者也可能是一个空指针。 通常返回一个非空指针,除非耗尽内存或发生了像不能把命令发送到服务器这样的严重错误。 应该调用PQresultStatus函数来检查任何错误的返回值 (包括空指针的值,在这种情况下它将返回PGRES_FATAL_ERROR)。 使用PQerrorMessage获取有关错误的更多信息。

命令字符串可以包括多个SQL命令(用分号分隔)。在一个PQexec 调用中发送的多个查询是在一个事务里处理的,除非在查询字符串里有明确的 BEGIN/COMMIT命令把整个字符串分隔成多个事务。 请注意,返回的PGresult结构只描述字符串里执行的最后一条命令的结果。 如果有一个命令失败,那么字符串处理的过程就会停止,并且返回的PGresult 会描述错误条件。

PQexecParams

        向服务器提交一条命令并且等待结果,还有独立于SQL命令文本传递参数的能力。

PGresult *PQexecParams(PGconn *conn,
                       const char *command,
                       int nParams,
                       const Oid *paramTypes,
                       const char * const *paramValues,
                       const int *paramLengths,
                       const int *paramFormats,
                       int resultFormat);

  PQexecParams类似PQexec,但是提供了额外的功能: 参数值可以独立于命令字符串进行声明,并且可以要求查询结果的格式是文本或二进制的。 PQexecParams只是在协议3.0及以后的版本中支持;在使用协议2.0的时候会失败。

        函数的参数是:

        conn

                连接对象通过它发送命令。

        command

                要执行的SQL命令字符串。如果使用参数,它们在命令字符串中被叫做$1、 $2等等。

        nParams

                提供的参数数目;它是paramTypes[]、paramValues[]、 paramLengths[]和paramFormats[]数组的长度。 (当nParams是0时,数组指针可以是NULL。)

        paramTypes[]

                通过OID,将声明数据类型指定到参数标记。如果paramTypes 是NULL,或数组中任何的特定参数是0,服务器为参数标记推断数据类型, 采用的方式与一个未定义类型的文本字符串相同。

        paramValues[]

                声明参数的实际值。在这个数组中的一个空指针表示相应的参数是空; 否则指针指向一个以零结尾的文本字符串(文本格式)或者服务器希望的格式的二进制数据 (二进制格式)。

        paramLengths[]

                为二进制格式的参数声明实际数据长度。该设置忽略空参数或文本格式的参数。 如果没有二进制参数,那么数组指针可以为空。

        paramFormats[]

                声明参数为文本(为相应参数在数组条目中放置一个0)还是二进制格式 (为相应参数在数组条目中放置一个1)。如果数组指针是空,那么所有参数被看做是文本字符串。

                以二进制格式传递的值需要能够被后台识别的内部表示。例如,整数必须以网络字节顺序来传递。 传递numeric值需要服务器存储格式的识别,如在 src/backend/utils/adt/numeric.c::numeric_send()和 src/backend/utils/adt/numeric.c::numeric_recv()中那样。

        resultFormat

                声明0用于以文本格式获得结果,或1用于以二进制格式获得结果。 (目前没有规定以不同的格式来获取不同的结果列,即使底层协议中可能实现。)

PQexecParams相比PQexec的主要优势是参数值可以从命令字符串中分离出来, 因此避免了繁琐和容易出错的引用和转义的需要。

PQexec不同的是,PQexecParams在一个给出的字符串里最多允许一个SQL命令。 (里面可以有分号,但是不得超过一个非空的命令。)这是下层协议的一个限制, 但是也有些好处,比如作为对SQL注入攻击的额外防御。

提示:

通过OID声明参数类型是非常繁琐的,尤其是你不希望在你的程序里写死特定的OID值的时候。 不过,你可以避免这么做,即使在服务器自己无法判断参数类型, 或者是选择了一种与你预期不同的参数类型的时候也一样。在SQL命令文本里, 给参数符号附加一个明确的类型转换,显示你准备发送的数据类型。比如:

SELECT * FROM mytable WHERE x = $1::bigint;

这样强制参数$1当作bigint看待,即使缺省情况下它会被赋予和 x一样的类型。在以二进制格式发送参数值的时候, 我们强烈建议通过这种方法或者是声明数字类型OID的方法强制类型判断, 因为二进制格式比文本格式少一些冗余,因此服务器就会少一些机会捕捉类型的错误匹配。

PQprepare

        用给定的参数提交请求,创建一个预备语句,然后等待结束。

PGresult *PQprepare(PGconn *conn,
                    const char *stmtName,
                    const char *query,
                    int nParams,
                    const Oid *paramTypes);

  PQprepare创建一个为后面PQexecPrepared执行用的预备语句。 这个特性允许那些重复使用的语句只分析和规划一次,而不是每次执行都分析规划。 只是在协议3.0和以后的连接里支持PQprepare;在使用2.0协议的时候,它会失败。

        这个函数从query字串里创建一个叫stmtName的预备语句, query必须只包含一个 SQL 命令。stmtName可以是"", 这样就创建一个无名的语句,这种情况下,任何前面存在的无名语句都会自动被代替; 否则,如果语句名已经在当前会话里定义,那就是一个错误。如果使用了参数, 那么在查询里它们引用成$1,$2等等。nParams 是参数的个数,参数的类型在数组paramTypes[]里事先声明好了。 (如果nParams是零,那么这个数组指针可以是NULL。) paramTypes[]用 OID 的方式声明与参数符号关联的数据类型。 如果paramTypes为NULL,或者数组中某个特定元素是零, 那么服务器将用处理无类型文本同样的方法给这个参数符号赋予数据类型。还有, 查询可以使用比nParams数值更大的参数符号编号; 也为这些符号推断数据类型。(参阅PQdescribePrepared 作为一个找出推断的什么类型的手段。)

        和PQexec相似,结果通常是一个PGresult对象, 其内容表明服务器端是成功还是失败。空的结果表示内存耗尽或者完全不能发送命令。 使用PQerrorMessage获取有关这类错误的更多信息。

用于PQexecPrepared的预备语句也可以通过执行SQL PREPARE语句来创建。 还有,尽管没有libpq函数可以删除一个预备语句, SQL DEALLOCATE语句却可以删除。

PQexecPrepared

        发送一个请求,执行一个带有给出参数的预备语句,并且等待结果。

PGresult *PQexecPrepared(PGconn *conn,
                         const char *stmtName,
                         int nParams,
                         const char * const *paramValues,
                         const int *paramLengths,
                         const int *paramFormats,
                         int resultFormat);

  PQexecPreparedPQexecParams类似, 但是要执行的命令是通过命名一个前面准备好的语句声明的,而不是给出一个查询字串。 这个特性允许那些要重复使用的命令只进行一次分析和规划,而不是每次执行都来一遍。 这个语句必须在当前会话的前面已经准备好。PQexecPrepared 只在协议 3.0 和以后的版本里支持;在使用 2.0 版本的协议的时候,它们会失败。

        参数和PQexecParams一样,只是给出的是一个预备语句的名字,而不是一个查询字串, 并且没有paramTypes[]参数(没必要,因为预备语句的参数类型是在创建的时候确定的)。

PQdescribePrepared

        提交请求以获取有关指定的预备语句的信息,并等待完成。

PGresult *PQdescribePrepared(PGconn *conn, const char *stmtName);

  PQdescribePrepared允许应用程序获取有关先前准备的语句的信息。 PQdescribePrepared只在协议 3.0 和以后的版本里支持; 在使用 2.0 版本的协议的时候,它们会失败。

        stmtName可以是""或NULL以指向未命名声明, 要么必须与现有的预备语句同名。成功时,会返回一个带有PGRES_COMMAND_OK 的PGresult。可以在这个PGresult中使用 PQnparamsPQparamtype 函数以获得预备语句的参数信息,同时PQnfields, PQfnamePQftype等函数提供声明的结果列(如果有)的信息。

PQdescribePortal

        提交请求以获取有关指定的端口的信息,并等待完成。

PGresult *PQdescribePortal(PGconn *conn, const char *portalName);

  PQdescribePortal允许应用程序获得关于之前创建的端口的信息。 (libpq不提供与端口的直接连接,但可以使用这个函数来检查 DECLARE CURSOR命令创建的游标的属性)。                        PQdescribePortal 只支持3.0及其之后的连接协议;当使用协议2.0时会失败。

        portalName可以是""或NULL以指向未命名声明, 要么必须与现有的预备语句同名。成功时,会返回一个带有PGRES_COMMAND_OK 的PGresult。可以在PGresult中使用 PQnfieldsPQfname, PQftype等函数获取端口的结果列(如果有)的信息。

PGresult 结构封装了服务器返回的结果。libpq应该小心维护 PGresult的抽象。使用下面的访问函数获取 PGresult的内容。避免直接引用PGresult 里面的字段,因为它们在未来版本里可能会被修改。

PQresultStatus

        返回命令的结果状态。

ExecStatusType PQresultStatus(const PGresult *res);

  PQresultStatus可以返回下面数值之一:

        PGRES_EMPTY_QUERY

                发送给服务器的字串是空的。

        PGRES_COMMAND_OK

                成功完成一个不返回数据的命令。

        PGRES_TUPLES_OK

                成功执行一个返回数据的查询(比如SELECT或者SHOW)。

        PGRES_COPY_OUT

                (从服务器)Copy Out (拷贝出)数据传输开始。

        PGRES_COPY_IN

                Copy In(拷贝入)(到服务器)数据传输开始。

        PGRES_BAD_RESPONSE

                服务器的响应无法理解。

        PGRES_NONFATAL_ERROR

                发生了一个非致命错误(通知或者警告)。

        PGRES_FATAL_ERROR

                发生了一个致命错误。

        PGRES_COPY_BOTH

                拷贝入/出(到和从服务器)数据传输开始。这个特性当前只用于流复制, 所以这个状态不会在普通应用中发生。

        PGRES_SINGLE_TUPLE

        PGresult包含一个来自当前命令的结果元组。 这个状态只在查询选择了单行模式时发生(参阅第五节)。

        如果结果状态是PGRES_TUPLES_OK或PGRES_SINGLE_TUPLE, 那么可以用下面的函数从查询的返回中抽取元组信息。注意一个碰巧检索了零条元组的 SELECT仍然显示PGRES_TUPLES_OK。 PGRES_COMMAND_OK用于不返回元组的命令(没有RETURNING 子句的INSERT,UPDATE等)。 返回PGRES_EMPTY_QUERY的响应通常意味着暴露了客户端软件里面的Bug。

        状态为PGRES_NONFATAL_ERROR的结果永远不会直接由PQexec 或者其它查询执行函数返回;这类的结果会被传递给通知处理器 (参阅第十二节)。

PQresStatus

        把PQresultStatus返回的枚举类型转换成一个描述状态码的字符串常量。 调用者不应该释放结果。

char *PQresStatus(ExecStatusType status);

PQresultErrorMessage

        返回与查询关联的错误消息,或在没有错误时返回一个空字符串。

char *PQresultErrorMessage(const PGresult *res);

        如果有错误,那么返回的字串将包括一个结尾的新行。调用者不应该直接释放结果。 在相关的PGresult句柄传递给PQclear之后,它会自动释放。

        紧跟在一个PQexecPQgetResult调用后面, PQerrorMessage(对连接)将返回与PQresultErrorMessage (对结果)一样的字符串。不过,一个PGresult将保有其错误消息直到被删除, 而连接的错误消息将在后续的操作完成时被改变。当你想知道与某个PGresult 相关联的状态时用PQresultErrorMessage; 当你想知道与连接的最近一个操作相关联的状态时用PQerrorMessage

PQresultErrorField

        返回一个独立的错误报告字段。

char *PQresultErrorField(const PGresult *res, int fieldcode);

        fieldcode是一个错误字段标识符;参阅下面列出的符号。 如果PGresult不是错误或者警告结果或者不包括指定的字段, 那么返回NULL。字段值通常将不包括结尾的新行。调用者不应该直接释放结果。 在相关联的PGresult句柄传递给PQclear之后,它将被自动释放。

        下列代码是可用的:

        PG_DIAG_SEVERITY

                严重程度,这个字段的内容是ERROR,FATAL或者PANIC (在错误消息里),或者WARNING,NOTICE,DEBUG, INFO或LOG(在通知消息里),或者是这些东西的一个本地化翻译。总是出现。

        PG_DIAG_SQLSTATE

                这个错误的SQLSTATE代码。SQLSTATE代码表示所发生的错误的类型; 可以由前端应用用于对特定的数据库错误执行特定的操作(比如错误处理)。 这个字段是不能区域化的,并且总是出现。

        PG_DIAG_MESSAGE_PRIMARY

                主要的人类可读错误的信息(通常一行)。总是出现。

        PG_DIAG_MESSAGE_DETAIL

                细节:一个可选的从属错误消息,里面有更多有关该问题的细节。可能有多行。

        PG_DIAG_MESSAGE_HINT

                提示:一个可选的有关如何处理该问题的建议。它和细节的区别是它提供了建议 (可能不太合适)而不光是事实。可能有好几行。

        PG_DIAG_STATEMENT_POSITION

                一个包含十进制整数的字串,表明错误游标的位置,作为一个索引指向最初的语句字符串。 第一个字符的索引是 1,并且这个位置是用字符计,而不是用字节计。

        PG_DIAG_INTERNAL_POSITION

                这个和PG_DIAG_STATEMENT_POSITION字段定义是一样的, 区别是它在游标位置指向内部生成的命令时使用,而不是客户端提交的命令。如果出现了这个字段, 那么PG_DIAG_INTERNAL_QUERY字段也总是出现。

        PG_DIAG_INTERNAL_QUERY

                一个失败的内部生成的命令的文本。比如,这个可能是一个 PL/pgSQL 函数发出的 SQL 查询。

        PG_DIAG_CONTEXT

                一个指示器,表明错误发生的环境。目前这个包括活跃的过程语言函数和内部生成的查询的调用堆栈跟踪。 跟踪是每行一条,最近的在上面。

        PG_DIAG_SCHEMA_NAME

                如果错误与特定的数据库对象相关,那么是包含该对象的模式名(如果有)。

        PG_DIAG_TABLE_NAME

                如果错误与特定的表相关,那么是该表的名字。(参考模式名字段获取表的模式的名字。)

        PG_DIAG_COLUMN_NAME

                如果错误与特定的表字段相关,那么是该字段的名字。(参考模式和表名字段识别该表。)

        PG_DIAG_DATATYPE_NAME

                如果错误与特定的数据类型相关,那么是该数据类型的名字。(参考模式名字段获取数据类型的模式的名字。)

        PG_DIAG_CONSTRAINT_NAME

                如果错误与特定的约束相关,那么是该约束的名字。参考上面列出的字段获取相关的表或域。 (为了这个目的,索引被看做是约束,即使它们是用约束语法创建的。)

        PG_DIAG_SOURCE_FILE

                报告错误的源代码所在的文件名。

        PG_DIAG_SOURCE_LINE

                报告错误的源代码所在的行号。

        PG_DIAG_SOURCE_FUNCTION

                报告错误的源代码函数的名字。

注意:

只为有限的错误类型提供模式名、表名、字段名、数据类型名和约束名字段;不要假设这些字段的出现会保证其他字段的出现。 核心错误来源观察以上提到的相互关系,但是用户定义的函数可以以其他方式使用这些字段。 同样的,不要假设这些字段表示当前数据库中的同时期对象。

        按照自身的要求格式化显示信息是客户端的责任;特别是根据需要对长行进行折行。 在错误消息字段里出现的新行字符应该当作分段符号,而不是换行。

        libpq生成的错误将会有严重性和主信息,但是通常没有其它字段。 3.0 协议之前返回的错误将包含严重性和主信息,有时候还有详细信息,但是没有其它字段。

        请注意这些错误字段只能从PGresult对象里获得, 而不是PGconn对象;没有PQerrorField函数。

PQclear

        释放与PGresult相关联的存储空间。 任何不再需要的查询结果都应该用PQclear释放掉。

void PQclear(PGresult *res);

        只要你需要,你可以保留PGresult对象任意长的时间; 当你提交新的查询时它并不消失,甚至你断开连接后也是这样。要删除它, 你必须调用PQclear。不这么做将导致你应用中的内存泄漏。

3.2 检索查询结果信息

这些函数用于从一个代表着成功查询结果(也就是说,状态为PGRES_TUPLES_OK或 PGRES_SINGLE_TUPLE的查询)的PGresult对象中抽取信息。 它们也可以用于从一个成功描述操作中抽取信息: 一个描述的结果和实际查询的执行将要提供的结果有所有相同的字段信息,但是它有零行。 对于其它状态值的对象,他们的行为会好像他们有零行和零列一样。

PQntuples

        返回查询结果里的行(元组)个数。因为它返回一个整数的结果,在32位操作系统上大型结果集可能溢出返回值。

int PQntuples(const PGresult *res);

PQnfields

        返回查询结果里数据行的列(字段)的个数。

int PQnfields(const PGresult *res);

PQfname

        返回与给出的字段编号相关联的字段名。字段编号从 0 开始。调用者不应该直接释放结果。 在相关联的PGresult句柄传递给PQclear之后,结果会被自动释放。

char *PQfname(const PGresult *res,
              int column_number);

如果字段编号超出范围,那么返回NULL。

PQfnumber

        返回与给出的字段名相关的字段编号。

int PQfnumber(const PGresult *res,
              const char *column_name);

        如果给出的名字不匹配任何字段,返回-1。

        给出的名字是当作 SQL 命令里的一个标识符看待的,也就是说,如果没有加双引号, 那么会转换为小写。比如,如果我们有一个从 SQL 命令里生成的查询结果:

SELECT 1 AS FOO, 2 AS "BAR";

        那么我们会有下面的结果:

PQfname(res, 0)              foo
PQfname(res, 1)              BAR
PQfnumber(res, "FOO")        0
PQfnumber(res, "foo")        0
PQfnumber(res, "BAR")        -1
PQfnumber(res, "\"BAR\"")    1

PQftable

        返回我们抓取的字段所在的表的 OID。字段编号从 0 开始。

Oid PQftable(const PGresult *res,
             int column_number);

如果字段编号超出了范围,或者声明的字段不是一个指向某个表的字段的简单引用, 或者使用了 3.0 版本之前的协议,那么就会返回InvalidOid。 你可以查询系统表pg_class来判断究竟引用了哪个表。

在你包含libpq头文件的时候,就会定义类型Oid 和常量InvalidOid。他们都是相同的整数类型。

PQftablecol

        返回组成声明的查询结果字段的字段号(在它的表内部)。查询结果字段编号从 0 开始, 但是表字段编号不会是 0。

int PQftablecol(const PGresult *res,
                int column_number);

         如果字段编号超出范围,或者声明的字段并不是一个表字段的简单引用, 或者使用的是 3.0 之前的协议,那么返回零。

PQfformat

        返回说明给出字段的格式的格式代码。字段编号从 0 开始。

int PQfformat(const PGresult *res,
              int column_number);

        格式码为 0 表示文本数据,而格式码是一表示二进制数据。(其它编码保留给将来定义。)

PQftype

        返回与给定字段编号关联的数据类型。返回的整数是一个该类型的内部 OID 号。字段编号从0 开始。

Oid PQftype(const PGresult *res,
            int column_number);

        你可以查询系统表pg_type以获取各种数据类型的名称和属性。 内建的数据类型的OID在源码树的 src/include/catalog/pg_type.h文件里定义。

PQfmod

        返回与给定字段编号相关联的字段的类型修饰符。字段编号从 0 开始。

int PQfmod(const PGresult *res,
           int column_number);

        类型修饰符的值是类型相关的;他们通常包括精度或者尺寸限制。 数值 -1 用于表示"没有可用信息"。大多数数据类型不用修饰词,这种情况下该值总是-1。

PQfsize

        返回与给定字段编号关联的字段以字节计的大小。字段编号从0 开始。

int PQfsize(const PGresult *res,
            int column_number);

  PQfsize返回在数据库行里面给该数据字段分配的空间, 换句话说就是该数据类型在服务器的内部表现形式的大小(尺寸)。(因此, 这个对客户端没有什么用。) 负值表示该数据类型是可变长度。

PQbinaryTuples

        如果PGresult包含二进制数据时返回 1,如果包含文本数据返回 0。

int PQbinaryTuples(const PGresult *res);

        这个函数已经废弃了(除了还用于与COPY连接之外), 因为我们可能在一个PGresult的某些字段里包含文本数据, 而另外一些字段包含二进制数据。更好的是使用PQfformat。 PQbinaryTuples只有在结果中的所有字段都是二进制(格式 1)的时候才返回 1。

PQgetvalue

        返回一个PGresult里面一行的单独的一个字段的值。 行和字段编号从 0 开始。调用者不应该直接释放结果。在把PGresult 句柄传递给PQclear之后,结果会被自动释放。

char *PQgetvalue(const PGresult *res,
                 int row_number,
                 int column_number);

        对于文本格式的数据,PQgetvalue返回的值是一个表示字段值的空(NULL) 结尾的字符串。对于二进制格式,返回的值就是由该数据类型的typsend 和typreceive决定的二进制表现形式。(在这种情况下, 数值实际上也跟着一个字节零,但是通常这个字节没什么用处,因为数值本身很可能包含内嵌的空。)

        如果字段值是空,则返回一个空字串。参阅PQgetisnull来区别空值和空字串值。

   PQgetvalue返回的指针指向一个本身是PGresult 结构的一部分的存储区域。我们不能更改它,并且如果我们要在PGresult 结构的生存期后还要使用它的话,我们必须明确地把该数值拷贝到其他存储器中。

PQgetisnull

        测试一个字段是否为空(NULL)。行和字段编号从 0 开始。

int PQgetisnull(const PGresult *res,
                int row_number,
                int column_number);

        如果该域包含 NULL,函数返回 1,如果包含非空(non-null )值,返回 0。 (注意,对一个 NULL 字段,PQgetvalue将返回一个空字符串,不是一个空指针。)

PQgetlength

        返回以字节计的字段的长度。行和字段编号从 0 开始。

int PQgetlength(const PGresult *res,
                int row_number,
                int column_number);

        这是特定数值的实际数据长度,也就是说,PQgetvalue指向的对象的大小。 对于文本数据格式,它和strlen()相同。对于二进制格式,这是基本信息。 请注意我们应该依靠PQfsize 获取实际数据长度。

PQnparams

        返回一个预备语句中的参数的数目。

int PQnparams(const PGresult *res);

        只有在检查PQdescribePrepared的结果时,这个函数是有用的。 对于其他类型的查询将返回零。

PQparamtype

        返回指示语句中的参数的数据类型。参数编号从0开始。

Oid PQparamtype(const PGresult *res, int param_number);

        只有在检查PQdescribePrepared的结果时,这个函数是有用的。 对于其他类型的查询将返回零。

PQprint

        向指定的输出流打印所有的行和(可选的)字段名称。

void PQprint(FILE *fout,      /* 输出流 */
             const PGresult *res,
             const PQprintOpt *po);
typedef struct
{
    pqbool  header;      /* 打印输出字段头和行计数 */
    pqbool  align;       /* 填充对齐字段 */
    pqbool  standard;    /* 旧的格式 */
    pqbool  html3;       /* 输出HTML表 */
    pqbool  expanded;    /* 扩展表 */
    pqbool  pager;       /* 必要时在输出中使用分页器 */
    char    *fieldSep;   /* 字段分隔符 */
    char    *tableOpt;   /* HTML表格元素的属性 */
    char    *caption;    /* HTML表标题 */
    char    **fieldName; /* 替换字段名组成的空结尾的数组 */
} PQprintOpt;

         这个函数以前被psql用于打印查询结果,但是现在已经不用这个函数了。 请注意它假设所有的数据都是文本格式。

3.3 检索其他命令的结果信息

这些函数用于从PGresult对象里检索其他信息。

PQcmdStatus

        返回产生PGresult的 SQL 命令的命令状态标签

char *PQcmdStatus(PGresult *res);

        通常这只是命令的名字,但是它可能包括额外的数据,比如处理过的行数。调用者不应该直接释放结果。 结果会在把PGresult句柄传递给PQclear的时候释放。

PQcmdTuples

        返回被 SQL 命令影响的行的数量。

char *PQcmdTuples(PGresult *res);

        这个函数返回一个字符串,包含PGresult产生的SQL语句影响的行数。 这个函数只能用于下列的执行:SELECT,CREATE TABLE AS,INSERT, UPDATE,DELETE,MOVE,FETCH,或者 COPY语句,或者是一个包含INSERT,UPDATE或DELETE 语句的预备查询的EXECUTE。如果生成这个PGresult的命令是其他的东西, 那么PQcmdTuples返回一个空字串。调用者不应该直接释放返回的数值。 在相关联的PGresult被传递给PQclear之后,它会被自动释放。

PQoidValue

        返回插入的行的OID, 如果SQL命令是INSERT,插入了正好一行到有OID的表格, 或者是一个包含合适INSERT语句的预备查询EXECUTE的时候。 否则,函数返回InvalidOid。如果受INSERT 影响的表不包含 OID,也返回InvalidOid。

Oid PQoidValue(const PGresult *res);

PQoidStatus

        为了支持PQoidValue,这个函数已经废弃了,并且不是线程安全的。 它返回插入行的带有OID的字符串,而PQoidValue返回OID值。

char *PQoidStatus(const PGresult *res);

 3.4 转义包含在SQL命令中的字符串

PQescapeLiteral

char *PQescapeLiteral(PGconn *conn, const char *str, size_t length);

  PQescapeLiteral为在 SQL 命令中使用字串而对之进行转义处理。 在我们向 SQL 命令里把数据值当作文本常量插入的时候很有用。有些字符 (比如单引号和反斜杠)必须被转义,以避免他们被 SQL 分析器作为特殊字符解析。 PQescapeLiteral执行这个操作。

  PQescapeLiteral返回一个内存中分配有malloc()的 str参数的转义版本。当结果不再需要时,需要通过PQfreemem() 来释放这块内存。不需要一个0字节结束,并且不应以length计数。 (如果在处理length字节之前出现0字节的结束,PQescapeLiteral 在此处结束;这个行为有点像strncpy)。返回的字符串中所有特殊字符都替换掉了, 因此可以很好的被PostgreSQL字符串文本解析器处理,同样, 允许增加一个0字节结尾。必须在PostgreSQL 字符串文本两边的单引号包含在结果字符串中。

        一旦错误,PQescapeLiteral返回NULL并在conn对象中存储合适的信息。

 提示:

处理从不可信来源收到的字符串时必须进行合适的转义,否则存在一定的安全风险: 容易受到"SQL 注入"攻击,数据库中会被写入未知的SQL命令。

        需要注意的是,当一个数据以PQexecParams或它的兄弟格式, 作为一个单独的参数传递时,做转义是不必要,也是不正确的。

PQescapeIdentifier

char *PQescapeIdentifier(PGconn *conn, const char *str, size_t length);

  PQescapeIdentifier转义一个字符串作为一个SQL标识符使用, 如一个表,列,或函数名。当一个用户自定义标识符需要包含特殊的字符, 否则将不能被SQL解析器解析为标识符的一部分时,或者当标识符需要包含大写字母, 且这种情况必须保留时,这样做是很有用的。

  PQescapeIdentifier返回str参数转义为一个内存中分配有 malloc()的SQL标识符的版本。当结果不再需要时,这块内存必须使用 PQfreemem()来释放。不需要一个0字节结束,并且不应以length计数。 (如果在处理length字节之前出现0字节的结束,PQescapeIdentifier 在此处结束;这个行为比较像strncpy)。返回的字符串中所有特殊字符都替换掉了, 因此可以很好的作为SQL标识符被处理。也可以添加一个结尾的0字节。返回字符串也是被双引号环绕。

        出错时,PQescapeIdentifier返回NULL,并且在conn对象中存贮合适的信息。

提示:

由于带有字符串常量,为阻止SQL注入攻击,当从一个不可信任资源获得时,SQL标识符必须转义。

PQescapeStringConn

size_t PQescapeStringConn(PGconn *conn,
                          char *to, const char *from, size_t length,
                          int *error);

  PQescapeStringConn转义字符串常量,比较像PQescapeLiteral。 不同于PQescapeLiteral,请求应该提供一个适当大小的缓冲区。更重要的是, PQescapeStringConn不会生成一个必须在PostgreSQL 字符串常量两端的单引号;SQL命令中应该提供,这样结果中会被插入。from 参数指向字符串的第一个字符(用以转义),length参数指出了在这个字符串中的字节数。 不需要一个0字节结束,并且不应以length计数。 (如果在处理length字节之前出现0字节的结束,PQescapeStringConn 在此处结束;这个行为比较像strncpy)。to应该指向一个包含至少多于两倍 length大小的缓冲区,要么就不会定义该行为。如果to和from 字符串交叠,那么也不会定义该行为。

        error参数非NULL,那么在成功的时候*error会被设置为零, 失败的时候设置为非0。目前唯一可能的错误条件涉及在源字符串中无效的多字节编码。 输出字符串同样产生错误,但服务器可以视其为异常以拒绝。一旦发生错误, 一条合适的信息会存储在conn对象中,无论error是否为NULL。

  PQescapeStringConn返回写到to的字节数,不包含0字节终止。

PQescapeString

  PQescapeString是一个老的,已经被PQescapeStringConn弃用了的版本。

size_t PQescapeString (char *to, const char *from, size_t length);

        与PQescapeStringConn唯一的不同是,PQescapeString 不使用PGconn或error参数。因此,不能够根据连接属性 (如字符编码)来调整其行为,因此可能会给出错误的结果,同样,不会报告错误条件。

  PQescapeString可以在客户端编程(一次只有一个PostgreSQL连接) 中安全的使用。在这种情况下,它可以找到"在屏幕背后"想要知道的。在其他情况下, 这是一个安全隐患,使用PQescapeStringConn时应该避免。

PQescapeByteaConn

        转义那些在 SQL 命令中使用的用bytea表示的二进制数据。 和PQescapeStringConn一样,这个函数只有在直接向 SQL 字串插入数据的时候使用。

unsigned char *PQescapeByteaConn(PGconn *conn,
                                 const unsigned char *from,
                                 size_t from_length,
                                 size_t *to_length);

        在SQL语句中用做bytea字串文本的一部分的时候, 有些字节值必需转义。PQescapeByteaConn转义字节使用十六进制编码或反斜杠转义。 

        from参数指向需要转义的字串的第一个字节,from_length 参数反映在这个二进制字串(结尾的字节零既不必要也不计算在内)里字节的个数。 to_length参数指向一个变量,它保存转义后字符串长度的结果。 结果字串长度包括结果结尾的零字节。

  PQescapeByteaConn在内存中返回一个from 参数的二进制字串的转义后的版本,这片内存是用malloc()分配的 在不再需要结果的时候,必须用PQfreemem()释放内存。 返回的字串已经把所有特殊的字符替换掉了,这样他们就可以由PostgreSQL 的字串文本分析器以及bytea的输入函数正确地处理。同时还追加了一个结尾的字节零。 那些必需包围在PostgreSQL字串文本周围的单引号并非结果字串的一部分。

        当出错时,返回一个空指针,一个合适的错误消息会被储存在conn对象中, 当前唯一可能的错误是结果字符串的内存不足。

PQescapeBytea

  PQescapeByteaPQescapeByteaConn的一个旧的,过时的版本。

unsigned char *PQescapeBytea(const unsigned char *from,
                             size_t from_length,
                             size_t *to_length);

        与PQescapeByteaConn唯一的不同之处在于,PQescapeByteaConn 不使用PGconn参数,因此,PQescapeBytea可以在客户端编程 (一次只有一个PostgreSQL连接)中安全的使用。在这种情况下, 它可以找到"在屏幕背后"想要知道的。如果在编程中使用多个数据库连接 (在这种情况下使用PQescapeByteaConn),那么可能会给出错误结果

PQunescapeBytea

        把一个二进制数据的字符串表现形式转换成二进制数据— PQescapeBytea的反作用。在以文本格式抽取bytea 数据的时候是必须的,但是在以二进制格式抽取的时候是不必要的。

unsigned char *PQunescapeBytea(const unsigned char *from, size_t *to_length);

        from参数指向一个字符串,比如应用到bytea字段时, PQgetvalue返回的。PQunescapeBytea 把它的字串表现形式转换成二进制形式,它返回一个用malloc() 分配的指向该缓冲区的指针,或者是出错时返回NULL,缓冲区的尺寸放在 to_length里。在不再需要这个结果之后, 这片内存必须用PQfreemem释放。

        这个转换不正好是PQescapeBytea逆转换,因为,当从 PQgetvalue接收时,字符串不希望被"转义"。尤其是,这意味着, 不需要考虑字符串引用,并且不需要PGconn参数。

四、异步命令处理

PQexec函数对于在普通的同步应用中提交命令是足以胜任的。不过,它的一些缺点可能对某些用户很重要:

  • PQexec会等待命令完成。该应用可能有其他的工作要做(例如维护用户界面),这时它将不希望阻塞等待回应。

  • 因为客户端应用的执行在它等待结果时会被挂起,对于应用来说很难决定要不要尝试取消正在进行的命令(这可以在一个信号处理器中完成,但别无他法)。

  • PQexec只能返回一个PGresult结构。如果提交的命令串包含多个SQL命令, 除了最后一个PGresult之外都会被PQexec丢弃。

  • PQexec总是收集命令的整个结果,把它缓存在一个单一的PGresult中。虽然这简化了应用的错误处理逻辑,它对于包含很多行的结果并不现实。

不想受到这些限制的应用可以改用构建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一次或者多次来获取结果。在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类似,它不能在 2.0 协议的连接上工作,并且它只允许在查询字符串中有一条命令。

PQsendPrepare

        发送一个请求用给定参数创建一个预备语句,而不等待完成。

int PQsendPrepare(PGconn *conn,
                  const char *stmtName,
                  const char *query,
                  int nParams,
                  const Oid *paramTypes);

        这个函数是PQprepare的异步版本:如果它能发送这个请求,则返回 1;如果不能,则返回 0。在成功调用之后,调用PQgetResult判断服务器是否成功创建了预备语句。这个函数的参数的处理和PQprepare一样。和PQprepare类似,它不能在 2.0 协议的连接上工作。

PQsendQueryPrepared

        发送一个请求用给定参数执行一个预备语句,而不等待结果。

int PQsendQueryPrepared(PGconn *conn,
                        const char *stmtName,
                        int nParams,
                        const char * const *paramValues,
                        const int *paramLengths,
                        const int *paramFormats,
                        int resultFormat);

        这个函数与PQsendQueryParams类似,但是要执行的命令是通过一个之前已经命名的预备语句指定, 而不是一个给出的查询字符串。该函数的参数处理和PQexecPrepared一样。和PQexecPrepared类似,它不能在 2.0 协议的连接上工作。

PQsendDescribePrepared

        发送一个请求获得指定的预备语句的信息,但不等待完成。

int PQsendDescribePrepared(PGconn *conn, const char *stmtName);

        这个函数是PQdescribePrepared的一个异步版本:如果它能够发送请求,则返回 1;否则,返回 0。在一次成功的调用后,调用PQgetResult来得到结果。该函数的参数处理和PQdescribePrepared一样。和PQdescribePrepared类似,它不能在 2.0 协议的连接上工作。

PQsendDescribePortal

        提交一个请求来获得关于指定入口的信息,但不等待完成。

int PQsendDescribePortal(PGconn *conn, const char *portalName);

        这个函数是PQdescribePortal的一个异步版本:如果它能够发送请求,则返回 1;否则,返回 0。在一次成功的调用后,调用PQgetResult来得到结果。该函数的参数处理和PQdescribePortal一样。和PQdescribePortal类似,它不能在 2.0 协议的连接上工作。

PQgetResult

        等待来自于一个之前的 PQsendQuery、 PQsendQueryParams、 PQsendPrepare、 PQsendQueryPrepared、 PQsendDescribePrepared或 PQsendDescribePortal调用的结果,并且返回它。当该命令完成并且没有更多结果时,将返回一个空指针。

PGresult *PQgetResult(PGconn *conn);

   PQgetResult必须被反复调用直到它返回一个空指针,空指针表示该命令完成(如果在没有命令活动时被调用,PQgetResult将立即返回一个空指针)。每一个来自PQgetResult的非空结果应该使用之前描述的同一个PGresult访问器处理。不要忘记在处理完之后释放每一个结果对象。注意,只有一个命令是活动的并且PQconsumeInput还没有读取必要的响应数据时, PQgetResult将会阻塞。

注意:
即使当PQresultStatus指出一个致命错误时,PQgetResult也应当被调用直到它返回一个空指针,以允许libpq完全处理该错误信息。

使用PQsendQueryPQgetResult解决了PQexec的一个问题:如果一个命令字符串包含多个SQL命令,这些命令的结果可以被个别地获得(顺便说一句:这样就允许一种简单的重叠处理形式, 客户端可以处理一个命令的结果,而同时服务器可以继续处理同一命令字符串中后面的查询)。

可以被PQsendQueryPQgetResult获得的另一种常常想要的特性是一次从大型结果中检索一行。这会在第五节中讨论。

如果只调用PQgetResult(不调用PQsendQuery等)将仍会导致客户端阻塞直到服务器完成下一个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消息(见第八节)。

        一个使用PQsendQuery/PQgetResult的客户端也可以尝试取消一个正在被服务器处理的命令,见第六节。但是,不管PQcancel的返回值是什么,应用都必须继续使用PQgetResult进行正常的结果读取序列。一次成功的取消只会导致命令比不取消时更快终止。

        通过使用上述函数,我们可以避免在等待来自数据库服务器的输入时被阻塞。不过,在应用发送输出给服务器时还是可能出现阻塞。这种情况比较少见,但是如果发送非常长的 SQL 命令或者数据值时确实可能发生(不过,最有可能是在应用通过COPY IN发送数据时)。为了避免这种可能性并且实现完全地非阻塞数据库操作,可以使用下列附加函数。

PQsetnonblocking

        把连接的状态设置为非阻塞。

int PQsetnonblocking(PGconn *conn, int arg);

        如果arg为 1,把连接状态设置为非阻塞;如果arg为 0,把连接状态设置为阻塞。如果 OK 返回 0,如果错误返回 -1。

        在非阻塞状态,调用 PQsendQueryPQputline、 PQputnbytesPQputCopyData和 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,应等待套接字变成读准备好并且接着按照上文所述读取响应。

五、一行一行地检索查询结果

通常,libpq会收集一个 SQL 命令的整个结果并且把它作为单个PGresult返回给应用。这对于返回大量行的命令是行不通的。对于这类情况,应用可以使用PQsendQueryPQgetResult单行模式。在这种模式中,结果行以一次一行的方式被返回给应用。

要进入到单行模式,在一次成功的PQsendQuery(或者其他兄弟函数)调用后立即调用PQsetSingleRowMode。这种模式选择只对当前正在执行的查询有效。然后反复调用PQgetResult,直到它返回空,如第四节中所示。如果该查询返回行,它们会作为单个的PGresult对象返回,它们看起来都像普通的查询结果,只不过其状态代码是PGRES_SINGLE_TUPLE而非PGRES_TUPLES_OK。在最后一行之后或者紧接着该查询返回零行之后,一个状态为PGRES_TUPLES_OK的零行对象会被返回,这就是代表不会有更多行的信号(但是注意仍然有必要继续调用PQgetResult直到它返回空)。所有这些PGresult对象将包含相同的行描述数据(列名、类型等等),这些数据和通常一个查询的PGresult对象的相同。每一个对象都应该按常规用PQclear释放。

PQsetSingleRowMode

        为当前正在执行的查询选择单行模式。

int PQsetSingleRowMode(PGconn *conn);

        这个函数只能在调用PQsendQuery或一个其兄弟函数之后立刻调用,并且要在任何连接上的其他操作之前调用,例如PQconsumeInputPQgetResult如果在正确的时间被调用,该函数会为当前查询激活单行模式并且返回 1。否则模式会保持不变并且该函数返回 0。在任何情况下,当前查询结束之后模式都会恢复到正常。

小心:
在处理一个查询时,服务器可能返回一些行并且接着遇到一个错误导致查询被中断。通常,libpq会丢弃掉这样的行并且至报告错误。但是在单行模式中,那些行(错误之前返回的行)已经被返回给应用。因此,应用将看到一些PGRES_SINGLE_TUPLE PGresult对象并且然后看到一个PGRES_FATAL_ERROR对象。为了得到正确的事务行为,如果查询最终失败,应用必须被设计为丢弃或者撤销使用之前处理的行完成的事情。

六、取消进行中的查询

一个客户端应用可以使用本节描述的函数请求取消一个仍在被服务器处理的命令。

PQgetCancel

        创建一个数据结构,这个数据结构包含取消一个通过特定数据库连接发出的命令所需要的信息。

PGcancel *PQgetCancel(PGconn *conn);

        给出一个PQgetCancel连接对象,PQgetCancel创建一个 PGcancel对象。如果给出 的conn是NULL或者是一个无效的连接,那么它将返回NULL。PGcancel对象是一个不透明的结构, 不应该为应用所直接访问;我们只能把它传递给PQcancel或者PQfreeCancel。 给定一个PGconn连接对象,PQgetCancel创建一个PGcancel对象。如果给定的connNULL或者一个不合法的连接,它将返回NULLPGcancel对象是一个透明的结构,它不能直接被应用访问。它只能被传递给PQcancelPQfreeCancel

PQfreeCancel

        释放一个由PQgetCancel创建的数据结构。

void PQfreeCancel(PGcancel *cancel);

        PQfreeCancel释放一个由前面的PQgetCancel创建的数据对象。 PQfreeCancel释放一个之前由PQgetCancel创建的数据对象。

PQcancel

        要求服务器放弃当前命令的处理。

int PQcancel(PGcancel *cancel, char *errbuf, int errbufsize);

        如果取消请求成功发送,则返回值为 1,否则为 0。如果不成功,则errbuf会被填充一个解释性的错误消息。errbuf必须是一个尺寸为errbufsize的字符数组(推荐尺寸为 256 字节)。,解释为何不成功。errbuf必须是 一个大小为errbufsize的 char 数组(建议大小为 256 字节)。

        不过,成功的发送并不保证请求会有任何效果。如果取消有效,那么当前的命令将提前终止并且返回一个错误结果。如果取消失败(也就是说, 因为服务器已经完成命令的处理),那么就根本不会有可见的结果。

        如果PQcancel是信号句柄里的一个局部变量,那么PQcancel可以在一个信号 句柄里安全地调用。在PQcancel涉及的范围里,PQcancel对象都是只读的, 因此我们也可以从一个与处理PGconn对象的线程分离的线程里处理它。 如果errbuf是信号处理器中的一个局部变量,PQcancel可以从一个信号处理器中安全地调用。在PGcancel有关的范围内,PQcancel都是只读的,因此也可以在一个从操纵PGconn对象的线程中独立出来的线程中调用它。

PQrequestCancel

    PQrequestCancelPQcancel的一个被废弃的变体。

int PQrequestCancel(PGconn *conn);

        要求服务器放弃当前命令的处理。它直接在PGconn对象上进行操作, 并且如果失败,就会在PGconn对象里存储错误消息(因此可以用PQerrorMessage检索出来)。 尽管功能相同,这个方法在多线程程序里和信号处理器里会带来危险,因为它可能 覆盖PGconn的错误消息,进而将当前连接上正在处理的操作搞乱。

七、快速路径接口

PostgreSQL提供一种快速路径接口来向服务器发送简单的函数调用。

提示:

这个接口在某种程度上已被废弃,因为我们可以通过创建一个定义该函数调用的预备语句来达到类似或者更强大的功能。然后,用参数和结果的二进制传输执行该语句,从而取代快速函数调用。

函数PQfn请求通过快速路径接口执行服务器函数。

PGresult *PQfn(PGconn *conn,
               int fnid,
               int *result_buf,
               int *result_len,
               int result_is_int,
               const PQArgBlock *args,
               int nargs);

typedef struct
{
    int len;
    int isint;
    union
    {
        int *ptr;
        int integer;
    } u;
} PQArgBlock;

fnid参数是要被执行的函数的 OID。argsnargs定义了要传递给函数的参数;它们必须匹配已声明的函数参数列表。当一个参数结构的isint域为真时,u.integer值被以指定长度(必须是 1、2 或者 4 字节)整数的形式发送给服务器;这时候会发生恰当的字节交换。当isint为假时,*u.ptr中指定数量的字节将不做任何处理被发送出去;这些数据必须是服务器 预期的用于该函数参数数据类型的二进制传输的格式(由于历史原因u.ptr被声明为类型int *,其实把它考虑成void *会更好)。result_buf是放置该函数返回值的缓冲区。调用者必须已经分配了足够的空间来存储返回值(这里没有检查!)。实际的结果长度将被放在result_len指向的整数中返回。如果预期结果是 2 或 4 字节整数,把result_is_int设为 1;否则设为 0。把result_is_int设为 1 导致libpq在必要时对值进行交换字节,这样它就作为对客户端机器正确的int值被传输,注意对任一种允许的结果大小都会传递一个 4 字节到*result_buf。当result_is_int是 0 时,服务器发送的二进制格式字节将不做修改直接返回(在这种情况下,把result_buf考虑为类型void *更好)。

PQfn总是返回一个有效的PGresult指针。在使用结果之前应该检查结果状态。当结果不再使用后,调用者有义务使用PQclear释放PGresult

注意我们没办法处理空参数、空结果,也没办法在使用这个接口时处理集值结果。

八、异步提示

PostgreSQL通过LISTENNOTIFY命令提供了异步通知。一个客户端会话用LISTEN命令在一个特定的通知频道中注册它感兴趣的通知(也可以用UNLISTEN命令停止监听)。当任何会话执行一个带有特定频道名的NOTIFY命令时,所有正在监听该频道的会话会被异步通知。可以传递一个“载荷”字符串来与监听者沟通附加的数据。

libpq应用把LISTENUNLISTENNOTIFY命令作为通常的 SQL 命令提交。 随后通过调用PQnotifies来检测NOTIFY消息的到达。

函数PQnotifies从来自服务器的未处理通知消息列表中返回下一个通知。如果没有待处理的信息则返回一个空指针。一旦PQnotifies返回一个通知,该通知会被认为已处理并且将被从通知列表中删除。

PGnotify *PQnotifies(PGconn *conn);

typedef struct pgNotify
{
    char *relname;              /* notification channel name */
    int  be_pid;                /* process ID of notifying server process */
    char *extra;                /* notification payload string */
} PGnotify;

在处理完PQnotifies返回的PGnotify对象后,别忘了用PQfreemem把它释放。释放PGnotify指针就足够了;relnameextra域并不代表独立分配的内存(这些域的名称是历史性的,尤其是频道名称与关系名称没有什么联系)。

下例给出了一个例子程序展示异步通知的使用。

/*
 * src/test/examples/testlibpq2.c
 *
 *
 * testlibpq2.c
 *      测试异步通知接口
 *
 * 开始这个程序,然后在另一个窗口的 psql 中做
 *   NOTIFY TBL2;
 * 重复四次来让这个程序退出。
 *
 * 或者,如果你想要得到奇妙的事情,尝试:
 * 用下列命令填充一个数据库
 * (在 src/test/examples/testlibpq2.sql 中提供)
 *
 *   CREATE SCHEMA TESTLIBPQ2;
 *   SET search_path = TESTLIBPQ2;
 *   CREATE TABLE TBL1 (i int4);
 *   CREATE TABLE TBL2 (i int4);
 *   CREATE RULE r1 AS ON INSERT TO TBL1 DO
 *     (INSERT INTO TBL2 VALUES (new.i); NOTIFY TBL2);
 *
 * 开始这个程序,然后从psql做下面的操作四次:
 *
 *   INSERT INTO TESTLIBPQ2.TBL1 VALUES (10);
 */
#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include "libpq-fe.h"
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

static void
exit_nicely(PGconn *conn)
{
    PQfinish(conn);
    exit(1);
}

int
main(int argc, char **argv)
{
    const char *conninfo;
    PGconn     *conn;
    PGresult   *res;
    PGnotify   *notify;
    int         nnotifies;

    /*
     * 用过用户在命令行上提供了一个参数,将它用作连接信息串。
     * 否则默认用设置 dbname=postgres 并且为所有其他链接参数使用环境变量或默认值。
     */
    if (argc > 1)
        conninfo = argv[1];
    else
        conninfo = "dbname = postgres";

    /* 建立一个到数据库的连接 */
    conn = PQconnectdb(conninfo);

    /* 检查后端连接是否成功建立 */
    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "Connection to database failed: %s",
                PQerrorMessage(conn));
        exit_nicely(conn);
    }

    /* 设置总是安全的搜索路径,这样恶意用户就无法取得控制。 */
    res = PQexec(conn,
                 "SELECT pg_catalog.set_config('search_path', '', false)");
    if (PQresultStatus(res) != PGRES_TUPLES_OK)
    {
        fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    /*
     * 任何时候不再需要 PGresult 时,应该 PQclear 它来避免内存泄露
     */
    PQclear(res);

    /*
     * 发出 LISTEN 命令启用来自规则的 NOTIFY 的通知。
     */
    res = PQexec(conn, "LISTEN TBL2");
    if (PQresultStatus(res) != PGRES_COMMAND_OK)
    {
        fprintf(stderr, "LISTEN command failed: %s", PQerrorMessage(conn));
        PQclear(res);
        exit_nicely(conn);
    }

    PQclear(res);

    /* 在接收到四个通知后退出。 */
    nnotifies = 0;
    while (nnotifies < 4)
    {
        /*
         * 休眠到在连接上发生某些事情。我们使用 select(2) 来等待输入,但是你也可以使用 poll() 或相似的设施。
         */
        int         sock;
        fd_set      input_mask;

        sock = PQsocket(conn);

        if (sock < 0)
            break;              /* 不应该发生 */

        FD_ZERO(&input_mask);
        FD_SET(sock, &input_mask);

        if (select(sock + 1, &input_mask, NULL, NULL, NULL) < 0)
        {
            fprintf(stderr, "select() failed: %s\n", strerror(errno));
            exit_nicely(conn);
        }

        /* 现在检查输入 */
        PQconsumeInput(conn);
        while ((notify = PQnotifies(conn)) != NULL)
        {
            fprintf(stderr,
                    "ASYNC NOTIFY of '%s' received from backend PID %d\n",
                    notify->relname, notify->be_pid);
            PQfreemem(notify);
            nnotifies++;
            PQconsumeInput(conn);
        }
    }

    fprintf(stderr, "Done.\n");

    /* 关闭到数据库的连接并且清理 */
    PQfinish(conn);

    return 0;
}

PQnotifies实际上并不从服务器读取数据;它只是返回被另一个libpq函数之前吸收的消息。在以前的libpq版本中,及时收到NOTIFY消息的唯一方法是持续地提交命令,即使是空命令也可以,并且在每次PQexec后检查PQnotifies。 虽然这个方法还能用,但是由于太过浪费处理能力已被废弃。

当你没有可用的命令提交时,一种更好的检查NOTIFY消息的方法是调用PQconsumeInput,然后检查PQnotifies。你可以使用select()来等待服务器数据到达,这样在无事可做时可以不浪费CPU能力(参考PQsocket来获得用于select()的文件描述符)。注意不管是用PQsendQuery/PQgetResult提交命令还是简单地使用PQexec,这种方法都能正常工作。不过,你应该记住在每次PQgetResultPQexec之后检查PQnotifies,看看在命令的处理过程中是否有通知到达。

九、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 表示整体拷贝格式都是二进制。

PQfformat

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

注意:

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

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命令预期的数据格式;

PQputCopyEnd

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

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

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

        如果终止消息被发送,则结果为 1;在非阻塞模式中,结果为 1 也可能只表示终止消息被成功地放在了发送队列中(在非阻塞模式中,要确认数据确实被发送出去,你应该接着等待写准备好并且调用PQflush,重复这些直到返回零)。零表示该函数由于缓冲区满而无法将该终止消息放在队列中,这只会发生在非阻塞模式中(在这种情况下,等待写准备好并且再次尝试PQputCopyEnd调用)。如果发生系统错误,则返回 -1,可以使用PQerrorMessage检索详情。

        在成功调用PQputCopyEnd之后,调用PQgetResult获取COPY命令的最终结果状态。我们可以用平常的方法来等待这个结果可用。然后返回到正常的操作。

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命令的最后结果状态。我们可以用平常的方法来等待这个结果可用。然后返回到正常的操作。

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命令时才能正确工作。

十、控制函数

这些函数控制libpq行为各种各样的细节。

PQclientEncoding

        返回客户端编码。

int PQclientEncoding(const PGconn *conn);

请注意,它返回的是编码 ID,而不是一个符号串字符串,如EUC_JP如果不成功,它会返回 -1。要把一个编码 ID 转换为为一个编码名称,可以用

char *pg_encoding_to_char(int encoding_id);

PQsetClientEncoding

        设置客户端编码。

int PQsetClientEncoding(PGconn *conn, const char *encoding);

    conn是一个到服务器的连接,而encoding是你想使用的编码。如果函数成功地设置编码,则返回 0,否则返回 -1。这个连接的当前编码可以使用PQclientEncoding确定。

PQsetErrorVerbosity

        决定PQerrorMessagePQresultErrorMessage返回的消息的细节程度。

typedef enum
{
    PQERRORS_TERSE,
    PQERRORS_DEFAULT,
    PQERRORS_VERBOSE
} PGVerbosity;

PGVerbosity PQsetErrorVerbosity(PGconn *conn, PGVerbosity verbosity);

        PQsetErrorVerbosity设置细节模式,并返回该连接的前一个设置。在TERSE模式下,返回的消息只包括严重性、主要文本以及位置;这些东西通常放在一个单一行中。缺省模式生成的消息包括上面的信息加上任何细节、提示或者上下文域(这些可能跨越多行)。VERBOSE模式包括所有可以可用的域。修改细节模式不会影响来自已有PGresult对象中的可用消息。只有随后创建的PGresult对象才受到影响(如果想要用不同的详细程度打印之前的错误,请见PQresultVerboseErrorMessage)。

PQsetErrorContextVisibility

        决定如何处理PQerrorMessagePQresultErrorMessage返回的消息中的CONTEXT域。

typedef enum
{
    PQSHOW_CONTEXT_NEVER,
    PQSHOW_CONTEXT_ERRORS,
    PQSHOW_CONTEXT_ALWAYS
} PGContextVisibility;

PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibility show_context);

    PQsetErrorContextVisibility设置上下文显示模式,返回该连接上之前的设置。这个模式控制消息中是否包括CONTEXT域(除非 verbosity 设置是TERSE,那种情况下CONTEXT不会被显示)。NEVER模式不会包括CONTEXT,而ALWAYS则尽可能地包括这个域。在ERRORS模式(默认)中,只在错误消息中包括CONTEXT域,而在通知和警告消息中不会包括。更改这个模式不会影响从已经存在的PGresult对象项中得到的消息,只会影响后续创建的PGresult对象(如果想要用不同的详细程度打印之前的错误,请见PQresultVerboseErrorMessage)。

PQtrace

        启用对客户端/服务器通讯的跟踪,把跟踪信息输出到一个调试文件流中。

void PQtrace(PGconn *conn, FILE *stream);

注意:

在 Windows上,如果libpq库和应用使用了不同的标志编译,那么这个函数调用会导致应用崩溃,因为FILE指针的内部表达是不一样的。特别是多线程/单线程、发布/调试 以及静态/动态标志应该是库和所有使用库的应用都一致。

PQuntrace

        禁用PQtrace打开的跟踪。

void PQuntrace(PGconn *conn);

十一、杂项函数

一如往常,总有一些函数不适合放在任何其他地方。

PQfreemem

        释放libpq分配的内存。

void PQfreemem(void *ptr);

        释放libpq分配的内存,尤其是PQescapeByteaConnPQescapeByteaPQunescapeByteaPQnotifies分配的内存。特别重要的是,在微软 Windows 上使用这个函数,而不是free()。这是因为只有 DLL 和应用的当多线程/单线程、发布/调试以及静态/动态标志相同时,才能在一个 DLL 中分配内存并且在应用中释放它。在非微软 Windows 平台上,这个函数与标准库函数free()相同。

PQconninfoFree

        释放PQconndefaultsPQconninfoParse分配的数据结构。

void PQconninfoFree(PQconninfoOption *connOptions);

        一个简单的PQfreemem不会做这些,因为数组包含对子字符串的引用。

PQencryptPasswordConn

        准备一个PostgreSQL口令的加密形式。

char *PQencryptPasswordConn(PGconn *conn, const char *passwd, const char *user, const char *algorithm);

        这个函数旨在用于那些希望发送类似于ALTER USER joe PASSWORD 'pwd'命令的客户端应用。不在这样一个命令中发送原始的明文密码是一个好习惯,因为它可能被暴露在命令日志、活动显示等等中。相反,在发送之前使用这个函数可以将口令转换为加密的形式。

        passwduser参数是明文口令以及用户的SQL名称。algorithm指定用来加密口令的加密算法。当前支持的算法是md5scram-sha-256onoff也被接受作为md5的别名,用于与较老的服务器版本兼容)。注意,对scram-sha-256支持是在PostgreSQL版本10中引入的,并且在老的服务器版本上无法工作。如果algorithmNULL,这个函数将向服务器查询password_encryption设置的当前值。这种行为可能会阻塞当前事务,并且当前事务被中止或者连接正忙于执行另一个查询时会失败。如果希望为服务器使用默认的算法但避免阻塞,应在调用PQencryptPasswordConn之前查询你自己的password_encryption,并且将该值作为algorithm传入。

        返回值是一个由malloc分配的字符串。调用者可以假设该字符串不含有需要转义的任何特殊字符。在处理完它之后,用PQfreemem释放结果。发生错误时,返回的是NULL,并且适当的消息会被存储在连接对象中。

PQencryptPassword

        准备一个PostgreSQL口令的md5加密形式。

char *PQencryptPassword(const char *passwd, const char *user);

        PQencryptPasswordPQencryptPasswodConn的一个较老的已经被废弃的版本。其差别是PQencryptPassword不要求一个连接对象,并且总是用md5作为加密算法。

PQmakeEmptyPGresult

        用给定的状态,构造一个空PGresult对象。

PGresult *PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status);

        这是libpq内部用于分配并初始化一个空PGresult对象的函数。如果不能分配内存,那么这个函数返回NULL。它也是可以对外使用的,因为一些应用认为它可以用于产生结果对象(特别是带有错误状态的对象)本身。如果conn非空,并且status表示一个错误,那么指定连接的当前错误消息会被复制到PGresult中。如果conn非空,那么连接中的任何已注册事件过程也会被复制到PGresult中(它们不会获得PGEVT_RESULTCREATE调用,但会看到PQfireResultCreateEvents)。注意在该对象上最终应该调用PQclear,正如对libpq本身返回的PGresult对象所作的那样。

PQfireResultCreateEvents

        为每一个在PGresult对象中注册的事件过程触发一个PGEVT_RESULTCREATE事件(见第十三节)。成功时返回非 0,如果任何事件过程失败则返回 0。

int PQfireResultCreateEvents(PGconn *conn, PGresult *res);

    conn参数被传送给事件过程,但不会被直接使用。如果事件过程不使用它,则会返回NULL

        已经接收到这个对象的PGEVT_RESULTCREATEPGEVT_RESULTCOPY事件的事件过程不会被再次触发。

        这个函数与PQmakeEmptyPGresult分开的主要原因是在调用事件过程之前创建一个PGresult并且填充它常常是合适的。

PQcopyResult

        为一个PGresult对象创建一个拷贝。这个拷贝不会以任何方式链接到源结果,并且当该拷贝不再需要时,必须调用PQclear进行清理。如果函数失败,返回NULL

PGresult *PQcopyResult(const PGresult *src, int flags);

        这个函数的意图并非是制作一个准确的拷贝。返回的结果总是会被放入PGRES_TUPLES_OK状态,并且不会拷贝来源中的任何错误消息(不过它确实会拷贝命令状态字符串)。flags参数决定还要拷贝些什么。它通常是几个标志的按位 OR。PG_COPYRES_ATTRS指定复制源结果的属性(列定义)。PG_COPYRES_TUPLES指定复制源结果的元组(这也意味着复制属性)。        PG_COPYRES_NOTICEHOOKS指定复制源结果的提醒钩子。PG_COPYRES_EVENTS指定复制源结果的事件(但是不会复制与源结果相关的实例数据)。

PQsetResultAttrs

        设置PGresult对象的属性。

int PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs);

        提供的attDescs被复制到结果中。如果attDescs指针为NULLnumAttributes小于1,那么请求将被忽略并且函数成功。如果res已经包含属性,那么函数会失败。如果函数失败,返回值是 0。如果函数成功,返回值是非 0。

PQsetvalue

        设置一个PGresult对象的一个元组域值。

int PQsetvalue(PGresult *res, int tup_num, int field_num, char *value, int len);

        这个函数将自动按需增加结果的内置元组数组。但是,tup_num参数必须小于等于PQntuples,意味着这个函数对元组数组一次只能增加一个元组。但已存在的任意元组中的任意域可以以任意顺序进行调整。如果field_num的一个值已经存在,它会被覆盖。如果len是 -1,或valueNULL, 该域值会被设置为一个 SQL 空值。value会被复制到结果的私有存储中,因此函数返回后就不再需要了。如果函数失败,返回值是 0。如果函数成功,返回值会是非 0。

PQresultAlloc

        为一个PGresult对象分配附属存储。

void *PQresultAlloc(PGresult *res, size_t nBytes);

        当res被清除时,这个函数分配的内存也会被释放掉。如果函数失败,返回值是NULL。结果被保证为按照数据的任意类型充分地对齐,正如malloc所作的。

PQlibVersion

        返回所使用的libpq版本。

int PQlibVersion(void);

        在运行时,这个函数的结果可以被用来决定在当前已载入的 libpq 版本中特定的功能是否可用。例如,这个函数可以被用来决定哪些选项可以被用于PQconnectdb

        结果通过将库的主版本号乘以10000再加上次版本号形成。例如,版本10.1将被返回为100001,而版本11.0将被返回为110000。

        在主版本10之前,PostgreSQL采用一种由三个部分组成的版本号,其中前两部分共同表示主版本。对于那些版本,PQlibVersion为每个部分使用两个数字,例如版本9.1.5将被返回为90105,而版本9.2.0将被返回为90200。

        因此,出于判断特性兼容性的目的,应用应该将PQlibVersion的结果除以100而不是10000来判断逻辑的主版本号。在所有的发行序列中,只有最后两个数字在次发行(问题修正发行)之间不同。

注意:
 

这个函数出现于PostgreSQL版本 9.1,因此它不能被用来在早期的版本中检测所需的功能,因为调用它将会创建一个对版本9.1及其后版本的链接依赖。

十二、通知处理

服务器产生的通知和警告消息不会被查询执行函数返回,因为它们不代表查询失败。它们可以被传递给一个通知处理函数,并且在处理者返回后执行会继续正常进行。默认的处理函数会把消息打印在stderr上,但是应用可以通过提供它自己的处理函数来重载这种行为。

由于历史原因,通知处理有两个级别,称为通知接收器和通知处理器。通知接收器的默认行为是格式化通知并且将一个字符串传递给通知处理器来打印。不过,如果一个应用选择提供自己的通知接收器,它通常会忽略通知处理器层并且在通知接收器中完成所有工作。

函数PQsetNoticeReceiver 为一个连接对象设置或者检查当前的通知接收器。相似地, PQsetNoticeProcessor 设置或检查当前的通知处理器。

typedef void (*PQnoticeReceiver) (void *arg, const PGresult *res);

PQnoticeReceiver
PQsetNoticeReceiver(PGconn *conn,
                    PQnoticeReceiver proc,
                    void *arg);

typedef void (*PQnoticeProcessor) (void *arg, const char *message);

PQnoticeProcessor
PQsetNoticeProcessor(PGconn *conn,
                     PQnoticeProcessor proc,
                     void *arg);

这些函数中的每一个会返回之前的通知接收器或处理器函数指针,并且设置新值。如果你提供了一个空函数指针,将不会采取任何动作,只会返回当前指针。

当接收到一个服务器产生的或者libpq内部产生的通知或警告消息,通知接收器函数会被调用。它会以一种PGRES_NONFATAL_ERROR PGresult的形式传递该消息(这允许接收器使用PQresultErrorField抽取个别的域,或者使用PQresultErrorMessage或者PQresultVerboseErrorMessage得到一个完整的预格式化的消息)。被传递给PQsetNoticeReceiver的同一个空指针也被传递(必要时,这个指针可以被用来访问应用相关的状态)。

默认的通知接收器会简单地抽取消息(使用PQresultErrorMessage)并且将它传递给通知处理器。

通知处理器负责处理一个以文本形式给出的通知或警告消息。该消息的字符串文本(包括一个收尾的新行)被传递给通知处理器,外加一个同时被传递给PQsetNoticeProcessor的空指针(必要时,这个指针可以被用来访问应用相关的状态)。

默认的通知处理器很简单:

static void
defaultNoticeProcessor(void *arg, const char *message)
{
    fprintf(stderr, "%s", message);
}

一旦你设定了一个通知接收器或处理器,你应该期待只要PGconn对象或者从它构造出的PGresult对象存在,该函数就应该能被调用。在一个PGresult创建时,PGconn的当前通知处理指针被复制到PGresult中,以备类似PQgetvalue的函数使用。

十三、 事件系统

libpq的事件系统被设计为通知已注册的事件处理器它感兴趣的libpq事件,例如PGconn以及PGresult对象的创建和毁灭。一种主要的使用情况是这允许应用将自己的数据与一个PGconn或者PGresult关联在一起,并且确保那些数据在适当的时候被释放。

每一个已注册的事件处理器与两部分数据相关,对于libpq它们只是透明的void *指针。当事件处理器被注册到一个PGconn时,会有一个应用提供的转移指针。该转移指针在PGconn及其产生的所有PGresult的生命期内都不会改变。因此,如果使用它,它必须指向长期存在的数据。此外,还有一个instance data指针,它在每一个PGconnPGresult中都开始于NULL。这个指针可以使用 PQinstanceData、 PQsetInstanceData、 PQresultInstanceData和 PQsetResultInstanceData函数操纵。注意和转移指针不同,一个PGconn的实例数据不会被从它创建的PGresult自动继承。libpq不知道转移和实例数据指针指向的是什么(如果有),并且将不会尝试释放它们 — 那是事件处理器的责任。

13.1. 事件类型

枚举PGEventId命名了事件系统处理的事件类型。它的所有值的名称都以PGEVT开始。对于每一种事件类型,都有一个相应的事件信息结构用来承载传递给事件处理器的参数。事件类型是:

PGEVT_REGISTER

        当PQregisterEventProc被调用时,注册事件会发生。这是一个初始化每一个事件过程都可能需要的instanceData的最佳时机。每个连接的每个事件处理器只会触发一个注册事件。如果该事件过程失败,注册会被中止。

typedef struct
{
    PGconn *conn;
} PGEventRegister;

        当收到一个PGEVT_REGISTER事件时,evtInfo指针应该被造型为PGEventRegister *。这个结构包含一个状态应该为CONNECTION_OKPGconn,保证在得到一个良好的PGconn之后能马上调用PQregisterEventProc。当返回一个失败代码时,所有的清理都必须被执行而不会发送PGEVT_CONNDESTROY事件。

PGEVT_CONNRESET

        连接重置事件在PQresetPQresetPoll完成时被触发。在两种情况中,只有重置成功才会触发该事件。如果事件过程失败,整个连接重置将失败,PGconn会被置为CONNECTION_BAD状态并且PQresetPoll将返回PGRES_POLLING_FAILED

typedef struct
{
    PGconn *conn;
} PGEventConnReset;

        当收到一个PGEVT_CONNRESET事件时,evtInfo指针应该被造型为PGEventConnReset *。尽管所包含的PGconn刚被重置,所有的事件数据还是保持不变。这个事件应该被用来重置/重载/重新查询任何相关的instanceData。注意即使事件过程无法处理PGEVT_CONNRESET,它仍将在连接被关闭时接收到一个PGEVT_CONNDESTROY事件。

PGEVT_CONNDESTROY

        为了响应PQfinish,连接销毁事件会被触发。由于 libpq 没有能力管理事件数据,事件过程有责任正确地清理它的事件数据。清理失败将会导致内存泄露。

typedef struct
{
    PGconn *conn;
} PGEventConnDestroy;

        当接收到一个PGEVT_CONNDESTROY事件时,evtInfo指针应该被造型为PGEventConnDestroy *。这个事件在PQfinish执行任何其他清理之前被触发。该事件过程的返回值被忽略,因为没有办法指示一个来自PQfinish的失败。还有,一个事件过程失败不该中断对不需要的内存的清理。

PGEVT_RESULTCREATE

        为了响应任何生成一个结果的查询执行函数,结果创建事件会被触发。这些函数包括PQgetResult。这个事件只有在结果被成功地创建之后才会被触发。

typedef struct
{
    PGconn *conn;
    PGresult *result;
} PGEventResultCreate;

        当接收到一个PGEVT_RESULTCREATE事件时,evtInfo指针应该被造型为PGEventResultCreate *conn是用来产生结果的连接。这是初始化任何需要与结果关联的instanceData的理想位置。如果该事件过程失败,结果将被清除并且失败将会被传播。该事件过程不能尝试自己PQclear结果对象。当返回一个失败代码时,所有清理必须被执行而不会发送PGEVT_RESULTDESTROY事件。

PGEVT_RESULTCOPY

        为了响应PQcopyResult,结果复制事件会被触发。这个事件只会在复制完成后才被触发。只有成功地处理了PGEVT_RESULTCREATEPGEVT_RESULTCOPY事件的事件过程才将会收到PGEVT_RESULTCOPY事件。

typedef struct
{
    const PGresult *src;
    PGresult *dest;
} PGEventResultCopy;

        当收到一个PGEVT_RESULTCOPY事件时,evtInfo指针应该被造型为PGEventResultCopy *src结果是要被复制的,而dest结果则是复制的目的地。这个事件可以被用来提供instanceData的一份深度副本,因为PQcopyResult没法这样做。如果该事件过程失败,整个复制操作将失败并且dest结果将被清除。当返回一个失败代码时,所有清理必须被执行而不会为目标结果发送PGEVT_RESULTDESTROY事件。

PGEVT_RESULTDESTROY

        为了响应PQclear,结果销毁事件会被触发。由于 libpq 没有能力管理事件数据,事件过程有责任正确地清理它的事件数据。清理失败将会导致内存泄露。

typedef struct
{
    PGresult *result;
} PGEventResultDestroy;

        当接收到一个PGEVT_RESULTDESTROY事件时,evtInfo指针应该被造型为PGEventResultDestroy *。这个事件在PQclear执行任何其他清理之前被触发。该事件过程的返回值被忽略,因为没有办法指示来自PQclear的失败。还有,一个事件过程失败不该中断不需要的内存的清理过程。

13.2. 事件回调函数

PGEventProc

    PGEventProc是到一个事件过程的指针的 typedef,也就是从 libpq 接收事件的用户回调函数。一个事件过程的原型必须是

int eventproc(PGEventId evtId, void *evtInfo, void *passThrough)

    evtId指示发生了哪一个PGEVT事件。evtInfo指针必须被造型为合适的结构类型才能获得关于事件的进一步信息。当事件过程已被注册时,passThrough参数是提供给PQregisterEventProc的指针。如果成功,该函数应该返回非零值,失败则返回零。

        在任何一个PGconn中,一个特定事件过程只能被注册一次。这是因为该过程的地址被用作查找键来标识相关的实例数据。

小心:
在 Windows 上,函数能够有两个不同的地址:一个对 DLL 之外可见而另一个对 DLL 之内可见。我们应当小心只有其中之一会被用于libpq的事件过程函数,否则将会产生混淆。编写代码的最简单规则是将所有的事件过程声明为static。如果过程的地址必须对它自己的源代码文件之外可见,提供一个单独的函数来返回该地址。

13.3. 事件支持函数

PQregisterEventProc

        为 libpq 注册一个事件回调过程。

int PQregisterEventProc(PGconn *conn, PGEventProc proc,
                        const char *name, void *passThrough);

        在每一个你想要接收事件的PGconn上必须注册一个事件过程。和内存不同,没有限制说一个连接上能注册多少个事件过程。如果该函数成功,它会返回一个非零值。如果它失败,则会返回零。

        当一个 libpq 事件被触发时,proc参数将被调用。它的内存地址也被用来查找instanceDataname参数被用来在错误消息中引用该事件过程。这个值不能是NULL或一个零长度串。名字串被复制到PGconn中,因此传递进来的东西不需要长期存在。当一个事件发生时,passThrough指针被传递给proc。这个参数可以是NULL

PQsetInstanceData

        设置连接conn的用于过程procinstanceDatadata。它在成功时返回非零值,失败时返回零(只有proc没有被正确地注册在conn中,才可能会失败)。

int PQsetInstanceData(PGconn *conn, PGEventProc proc, void *data);

PQinstanceData

        返回连接conn的与过程proc相关的instanceData,如果没有则返回NULL

void *PQinstanceData(const PGconn *conn, PGEventProc proc);

PQresultSetInstanceData

        把结果的用于procinstanceData设置为data。成功返回非零,失败返回零(只有proc没有被正确地注册在conn中,才可能会失败)。

int PQresultSetInstanceData(PGresult *res, PGEventProc proc, void *data);

PQresultInstanceData

        返回结果的与过程proc相关的instanceData,如果没有则返回NULL

void *PQresultInstanceData(const PGresult *res, PGEventProc proc);

13.4. 事件实例

这里是一个管理与 libpq 连接和结果相关的私有数据的例子的框架。


/* 要求 libpq 事件的头文件(注意:包括 libpq-fe.h) */
#include <libpq-events.h>

/* instanceData */
typedef struct
{
    int n;
    char *str;
} mydata;

/* PGEventProc */
static int myEventProc(PGEventId evtId, void *evtInfo, void *passThrough);

int
main(void)
{
    mydata *data;
    PGresult *res;
    PPGconn *conn =
        PQconnectdb("dbname=postgres options=-csearch_path=");

    if (PQstatus(conn) != CONNECTION_OK)
    {
        fprintf(stderr, "Connection to database failed: %s",
                PQerrorMessage(conn));
        PQfinish(conn);
        return 1;
    }

    /* 在任何应该接收事件的连接上调用一次。
     * 发送一个 PGEVT_REGISTER 给 myEventProc。
     */
    if (!PQregisterEventProc(conn, myEventProc, "mydata_proc", NULL))
    {
        fprintf(stderr, "Cannot register PGEventProc\n");
        PQfinish(conn);
        return 1;
    }

    /* conn 的 instanceData 可用 */
    data = PQinstanceData(conn, myEventProc);

    /* 发送一个 PGEVT_RESULTCREATE 给 myEventProc */
    res = PQexec(conn, "SELECT 1 + 1");

    /* 结果的 instanceData 可用 */
    data = PQresultInstanceData(res, myEventProc);

    /* 如果使用了 PG_COPYRES_EVENTS,发送一个 PGEVT_RESULTCOPY 给 myEventProc */
    res_copy = PQcopyResult(res, PG_COPYRES_TUPLES | PG_COPYRES_EVENTS);

    /* 如果在 PQcopyResult 调用时使用了 PG_COPYRES_EVENTS,结果的 instanceData 可用。*/
    data = PQresultInstanceData(res_copy, myEventProc);

    /* 两个清除都发送一个 PGEVT_RESULTDESTROY 给 myEventProc */
    PQclear(res);
    PQclear(res_copy);

    /* 发送一个 PGEVT_CONNDESTROY 给 myEventProc */
    PQfinish(conn);

    return 0;
}

static int
myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
{
    switch (evtId)
    {
        case PGEVT_REGISTER:
        {
            PGEventRegister *e = (PGEventRegister *)evtInfo;
            mydata *data = get_mydata(e->conn);

            /* 将应用相关的数据与连接关联起来 */
            PQsetInstanceData(e->conn, myEventProc, data);
            break;
        }

        case PGEVT_CONNRESET:
        {
            PGEventConnReset *e = (PGEventConnReset *)evtInfo;
            mydata *data = PQinstanceData(e->conn, myEventProc);

            if (data)
              memset(data, 0, sizeof(mydata));
            break;
        }

        case PGEVT_CONNDESTROY:
        {
            PGEventConnDestroy *e = (PGEventConnDestroy *)evtInfo;
            mydata *data = PQinstanceData(e->conn, myEventProc);

            /* 因为连接正在被销毁,释放示例数据 */
            if (data)
              free_mydata(data);
            break;
        }

        case PGEVT_RESULTCREATE:
        {
            PGEventResultCreate *e = (PGEventResultCreate *)evtInfo;
            mydata *conn_data = PQinstanceData(e->conn, myEventProc);
            mydata *res_data = dup_mydata(conn_data);

            /* 把应用相关的数据与结果(从 conn 复制过来)关联起来 */
            PQsetResultInstanceData(e->result, myEventProc, res_data);
            break;
        }

        case PGEVT_RESULTCOPY:
        {
            PGEventResultCopy *e = (PGEventResultCopy *)evtInfo;
            mydata *src_data = PQresultInstanceData(e->src, myEventProc);
            mydata *dest_data = dup_mydata(src_data);

            /* 把应用相关的数据与结果(从一个结果复制过来)关联起来 */
            PQsetResultInstanceData(e->dest, myEventProc, dest_data);
            break;
        }

        case PGEVT_RESULTDESTROY:
        {
            PGEventResultDestroy *e = (PGEventResultDestroy *)evtInfo;
            mydata *data = PQresultInstanceData(e->result, myEventProc);

            /* 因为结果正在被销毁,释放实例数据 */
            if (data)
              free_mydata(data);
            break;
        }

        /* 未知事件 ID,只返回true。 */
        default:
            break;
    }

    return true; /* 事件处理成功 */
}

十四、环境变量

下列环境变量能被用于选择默认的连接参数值,如果调用代码没有直接指定值,它们将被用于PQconnectdbPQsetdbLoginPQsetdb。例如,这些有助于防止数据库连接信息被硬编码到简单的客户端应用中。

  • PGHOST的行为和host连接参数相同。

  • PGHOSTADDR的行为和hostaddr连接参数相同。可以设置它来替代或者作为PGHOST的补充来防止 DNS 查找负担。

  • PGPORT的行为和port连接参数相同。

  • PGDATABASE的行为和dbname连接参数相同。

  • PGUSER的行为和user连接参数相同。

  • PGPASSWORD的行为和password连接参数相同。出于安全原因,我们不推荐使用这个环境变量,因为某些操作系统允许非根用户通过ps看到进程的环境变量。可以考虑使用一个口令文件(见第十五节)。

  • PGPASSFILE的行为和passfile连接参数相同。

  • PGSERVICE的行为和service连接参数相同。

  • PGSERVICEFILE指定针对每个用户的连接服务文件名。如果没有设置,默认为~/.pg_service.conf(见第十六节)。

  • PGOPTIONS的行为和options连接参数相同。

  • PGAPPNAME的行为和application_name连接参数相同。

  • PGSSLMODE的行为和sslmode连接参数相同。

  • PGREQUIRESSL的行为和requiressl连接参数相同。为了支持PGSSLMODE变量,这个环境变量已被废弃。同时设置两个变量会抑制这一个的效果。

  • PGSSLCOMPRESSION的行为和sslcompression连接参数相同。

  • PGSSLCERT的行为和sslcert连接参数相同。

  • PGSSLKEY的行为和sslkey连接参数相同。

  • PGSSLROOTCERT的行为和sslrootcert连接参数相同。

  • PGSSLCRL的行为和sslcrl连接参数相同。

  • PGREQUIREPEER的行为和requirepeer连接参数相同。

  • PGKRBSRVNAME的行为和krbsrvname连接参数相同。

  • PGGSSLIB的行为和gsslib连接参数相同。

  • PGCONNECT_TIMEOUT的行为和connect_timeout连接参数相同。

  • PGCLIENTENCODING的行为和client_encoding连接参数相同。

  • PGTARGETSESSIONATTRS的行为和target_session_attrs连接参数相同。

下面的环境变量可用来为每一个PostgreSQL会话指定默认行为(为每一个用户或每一个数据库设置默认行为的方法还可见ALTER ROLEALTER DATABASE命令)。

  • PGDATESTYLE设置日期/时间表示的默认风格(等同于SET datestyle TO ...)。

  • PGTZ设置默认的时区(等同于SET timezone TO ...)。

  • PGGEQO为遗传查询优化器设置默认模式(等同于SET geqo TO ...)。

这些环境变量的正确值可参考SQL 命令 SET

下面的环境变量决定libpq的内部行为,它们会覆盖编译在程序中的默认值。

  • PGSYSCONFDIR设置包含pg_service.conf文件以及未来版本中可能出现的其他系统范围配置文件的目录。

  • PGLOCALEDIR设置包含用于消息本地化的locale文件的目录。

十五、口令文件

一个用户主目录中的.pgpass文件能够包含在连接需要时使用的口令(并且其他情况不会指定口令)。在微软的 Windows 上该文件被命名为%APPDATA%\postgresql\pgpass.conf(其中%APPDATA%指的是用户配置中的应用数据子目录)。另外,可以使用连接参数passfile或者环境变量PGPASSFILE指定一个口令文件。

这个文件应该包含下列格式的行:

hostname:port:database:username:password

(你可以向该文件增加一个提醒:把上面的行复制到该文件并且在前面加上#)。前四个域的每一个都可以是文字值或者匹配任何东西的*。第一个匹配当前连接参数的行中的口令域将被使用(因此,在使用通配符时把更特殊的项放在前面)。如果一个条目需要包含:或者\,用\对该字符转义。如果指定了host连接参数,主机名字段会被匹配到host,否则如果指定了hostaddr参数则匹配到hostaddr,如果两者都没有给出,则会搜索主机名localhost。当连接是一个Unix域套接字连接并且host参数匹配libpq的默认套接字目录路径时,也会搜索主机名localhost。在一台后备服务器上,值为replication的数据库字段匹配连接到主服务器的里复制连接。否则数据库字段的用途有限,因为用户对同一个集簇中的所有数据库都有相同的口令。

在 Unix 系统上,口令文件上的权限必须不允许所有人或组内访问,可以用chmod 0600 ~/.pgpass这样的命令实现。如果权限没有这么严格,该文件将被忽略。在微软 Windows 上,该文件被假定存储在一个安全的目录中,因此不会进行特别的权限检查。

十六、连接服务文件

连接服务文件允许 libpq 连接参数与一个单一服务名称关联。那个服务名称可以被一个 libpq 连接指定,与其相关的设置将被使用。这允许在不重新编译 libpq 应用的前提下修改连接参数。服务名称也可以被使用PGSERVICE环境变量来指定。

连接服务文件可以是每个用户都有一个的服务文件,它位于~/.pg_service.conf或者环境变量PGSERVICEFILE指定的位置。它也可以是一个系统范围的文件,位于`pg_config --sysconfdir`/pg_service.conf的或者环境变量PGSYSCONFDIR指定的目录。如果相同名称的服务定义存在于用户和系统文件中,用户文件将优先考虑。

该文件使用一种“INI 文件”格式,其中小节名是服务名并且参数是连接参数。列表见上面第1.2节。例如:

# comment
[mydb]
host=somehost
port=5433
user=admin

share/pg_service.conf.sample中提供了一个例子文件。

十七、连接参数的 LDAP 查找

如果libpq已经在编译时打开了 LDAP 支持(configure的选项--with-ldap),就可以通过 LDAP 从一个中央服务器检索hostdbname之类的连接参数。这样做的好处是如果一个数据库的连接参数改变,不需要在所有的客户端机器上更新连接信息。

LDAP 连接参数查找使用连接服务文件pg_service.conf(见第十六节)。pg_service.conf中一个以ldap://开始的行将被识别为一个 LDAP URL 并且将执行一个 LDAP 查询。结果必须是一个keyword = value对列表,它将被用来设置连接选项。URL 必须遵循 RFC 1959 并且是形式

ldap://[hostname[:port]]/search_base?attribute?search_scope?filter

其中hostname默认为localhost并且port默认为 389。

一次成功的 LDAP 查找后,pg_service.conf的处理被终止。但是如果联系不上 LDAP 则会继续处理pg_service.conf。这就提供了后手,可以加入更多指向不同 LDAP 服务器的 LDAP URL 行、经典的keyword = value对或者默认连接选项。如果你宁愿在这种情况下得到一个错误消息,在该 LDAP URL 之后增加一个语法错误的行。

一个和 LDIF 文件一起创建的 LDAP 条目实例

version:1
dn:cn=mydatabase,dc=mycompany,dc=com
changetype:add
objectclass:top
objectclass:groupOfUniqueNames
cn:mydatabase
uniqueMember:host=dbserver.mycompany.com
uniqueMember:port=5439
uniqueMember:dbname=mydb
uniqueMember:user=mydb_user
uniqueMember:sslmode=require

可以用下面的 LDAP URL 查询:

ldap://ldap.mycompany.com/dc=mycompany,dc=com?uniqueMember?one?(cn=mydatabase)

你也可以将常规的服务文件条目和 LDAP 查找混合。pg_service.conf中一节的完整例子:

version:1
dn:cn=mydatabase,dc=mycompany,dc=com
changetype:add
objectclass:top
objectclass:device
cn:mydatabase
description:host=dbserver.mycompany.com
description:port=5439
description:dbname=mydb
description:user=mydb_user
description:sslmode=require

可以用下面的 LDAP URL 查询到:

ldap://ldap.mycompany.com/dc=mycompany,dc=com?description?one?(cn=mydatabase)

十八、SSL 支持 

PostgreSQL本地支持使用SSL连接加密客户端/服务器通信以提高安全性。

libpq读取系统范围的OpenSSL配置文件。默认情况下,这个文件被命名为openssl.cnf并且位于openssl version -d所报告的目录中。可以通过设置环境变量OPENSSL_CONF把这个默认值覆盖为想要的配置文件的名称。

18.1. 服务器证书的客户端验证

默认情况下,PostgreSQL将不会执行服务器证书的任何验证。这意味着可以在不被客户端知晓的情况下伪造服务器身份(例如通过修改一个 DNS 记录或者接管服务器的 IP 地址)。为了阻止哄骗,客户端必须能够通过一条信任链验证服务器的身份。信任链可以这样建立:在一台计算机上放置一个根(自签名的)证书机构(CA)的证书并且在另一台计算机上放置一个由根证书签发的叶子证书。还可以使用一种“中间”证书,它由根证书签发并且可以签发叶子证书。

为了允许客户端验证服务器的身份,在客户端上放置一份根证书并且在服务器上放置由根证书签发的叶子证书。为了允许服务器验证客户端的身份,在服务器上放置一份根证书并且在客户端上放置由根证书签发的叶子证书。也可以使用一个或者更多个中间证书(通常与叶子证书存在一起)来将叶子证书链接到根证书。

一旦信任链被建立起来,客户端有两种方法验证服务器发过来的叶子证书。如果参数sslmode被设置为verify-ca,libpq将通过检查该证书是否链接到存储在客户端上的根证书来验证服务器。如果sslmode被设置为verify-full,libpq将验证服务器的主机名匹配存储在服务器证书中的名称。如果服务器证书无法被验证,则SSL连接将失败。在大部分对安全性很敏感的环境中,推荐使用verify-full

verify-full模式中,主机名被拿来与证书的主体别名属性 匹配,或者在不存在类型dNSName的主体别名时与通用名称属性匹配。 如果证书的名称属性以一个星号(*)开始,这个星号将被 视作一个通配符,它将匹配所有除了句点(.) 之外的字符。这意味着该证书将不会匹配子域。如果连接是使用一个 IP 地址而不是一个主机名创建的,该 IP 地址将被匹配(不做任何 DNS 查找)。

要允许服务器证书验证,必须将一个或者更多个根证书放置在用户主目录下的~/.postgresql/root.crt文件中(在Microsoft Windows上该文件名为%APPDATA%\postgresql\root.crt)。如果需要把服务器发来的证书链链接到存储在客户端的根证书,还应该将中间证书加到该文件中。

如果文件~/.postgresql/root.crl存在(微软 Windows 上的%APPDATA%\postgresql\root.crl),证书撤销列表(CRL)项也会被检查。

根证书文件和 CRL 的位置可以通过设置连接参数sslrootcertsslcrl或环境变量PGSSLROOTCERTPGSSLCRL改变。

注意:

为了与 PostgreSQL 的早期版本达到向后兼容,如果存在一个根 CA 文件,sslmode=require的行为将与verify-ca相同,即服务器证书根据 CA 验证。我们鼓励依赖这种行为,并且需要证书验证的应用应该总是使用verify-ca或者verify-full

18.2. 客户端证书

如果服务器尝试通过请求客户端的叶子证书来验证客户端的身份,libpq将发送用户主目录下文件~/.postgresql/postgresql.crt中存储的证书。该证书必须链接到该服务器信任的根证书。也必须存在一个匹配的私钥文件~/.postgresql/postgresql.key。该私钥文件不能允许全部用户或者组用户的任何访问,可以通过命令chmod 0600 ~/.postgresql/postgresql.key实现。在微软 Windows 上这些文件被命名为%APPDATA%\postgresql\postgresql.crt%APPDATA%\postgresql\postgresql.key,不会有特别的权限检查,因为该目录已经被假定为安全。证书和密钥文件的位置可以使用连接参数sslcertsslkey或者环境变量PGSSLCERTPGSSLKEY覆盖。

postgresql.crt中的第一个证书必须是客户端的证书,因为它必须匹配客户端的私钥。可以选择将“中间”证书追加到该文件 — 这样做避免了在服务器上存放中间证书的要求(ssl_ca_file)。

18.3. 不同模式中提供的保护

sslmode参数的不同值提供了不同级别的保护。SSL 能够针对三类攻击提供保护:

窃听

        如果一个第三方能够检查客户端和服务器之间的网络流量,它能读取连接信息(包括用户名和口令)以及被传递的数据。SSL使用加密来阻止这种攻击。

中间人(MITM)

        如果一个第三方能对客户端和服务器之间传送的数据进行修改,它就能假装是服务器并且因此能看见并且修改数据,即使这些数据已被加密。然后第三方可以将连接信息和数据转送给原来的服务器,使得它不可能检测到攻击。这样做的通常途径包括 DNS 污染和地址劫持,借此客户端被重定向到一个不同的服务器。还有几种其他的攻击方式能够完成这种攻击。SSL使用证书验证让客户端认证服务器,就可以阻止这种攻击。

模仿

        如果一个第三方能假装是一个授权的客户端,它能够简单地访问它本不能访问的数据。通常这可以由不安全的口令管理所致。SSL使用客户端证书来确保只有持有合法证书的客户端才能访问服务器,这样就能阻止这种攻击。

对于一个已知安全的连接,在连接被建立之前,SSL 使用必须被配置在客户端和服务器之上。如果只在服务器上配置,客户端在知道服务器要求高安全性之前可能会结束发送敏感信息(例如口令)。在 libpq 中,要确保连接安全,可以设置sslmode参数为verify-fullverify-ca并且为系统提供一个根证书用来验证。这类似于使用一个https URL进行加密网页浏览。

一旦服务器已经被认证,客户端可以传递敏感数据。这意味着直到这一点,客户端都不需要知道是否证书将被用于认证,这样只需要在服务器配置中指定就比较安全。

所有SSL选项都带来了加密和密钥交换的负荷,因此必须在性能和安全性之间做出平衡。表18.3.1不同sslmode值所保护的风险,以及它们是怎样看待安全性和负荷的。

表 18.3.1. SSL 模式描述

sslmode窃听保护MITM保护声明
disableNoNo我不关心安全性,并且我不想为加密增加负荷。
allow可能No我不关心安全性,但如果服务器坚持,我将承担加密带来的负荷。
prefer可能No我不关心安全性,但如果服务器支持,我希望承担加密带来的负荷。
requireYesNo我想要对数据加密,并且我接受因此带来的负荷。我信任该网络会保证我总是连接到想要连接的服务器。
verify-caYes取决于 CA-策略我想要对数据加密,并且我接受因此带来的负荷。我想要确保我连接到的是我信任的服务器。
verify-fullYesYes我想要对数据加密,并且我接受因此带来的负荷。我想要确保我连接到的是我信任的服务器,并且就是我指定的那一个。

verify-caverify-full之间的区别取决于根CA的策略。如果使用了一个公共CAverify-ca允许连接到那些可能已经被其他人注册到该CA的服务器。在这种情况下,总是应该使用verify-full。如果使用了一个本地CA或者甚至是一个自签名的证书,使用verify-ca常常就可以提供足够的保护。

sslmode的默认值是prefer。如表中所示,这在安全性的角度来说没有意义,并且它只承诺可能的性能负荷。提供它作为默认值只是为了向后兼容,并且我们不推荐在安全部署中使用它。

18.4. SSL 客户端文件使用

表18.4.1总结了与客户端 SSL 设置相关的文件。

表 18.4.1 Libpq/客户端 SSL 文件用法

文件内容效果
~/.postgresql/postgresql.crt客户端证书由服务器要求
~/.postgresql/postgresql.key客户端私钥证明客户端证书是由拥有者发送;不代表证书拥有者可信
~/.postgresql/root.crt可信的证书机构检查服务器证书是由一个可信的证书机构签发
~/.postgresql/root.crl被证书机构撤销的证书服务器证书不能在这个列表上

18.5. SSL 库初始化

如果你的应用初始化libssllibcrypto库以及带有SSL支持的libpq,你应该调用PQinitOpenSSL来告诉libpq:libssllibcrypto库已经被你的应用初始化,这样libpq将不会也去初始化那些库。 关于 SSL API 详见http://h71000.www7.hp.com/doc/83final/ba554_90007/ch04.html

PQinitOpenSSL

        允许应用选择要初始化哪个安全性库。

void PQinitOpenSSL(int do_ssl, int do_crypto);

        当do_ssl是非零时,libpq将在第一次打开数据库连接前初始化OpenSSL库。当do_crypto是非零时,libcrypto库将被初始化。默认情况下(如果没有调用PQinitOpenSSL),两个库都会被初始化。当 SSL 支持没有被编译时,这个函数也存在但是什么也不做。

        如果你的应用使用并且初始化OpenSSL或者它的底层libcrypto库,你必须在第一次打开数据库连接前以合适的非零参数调用这个函数。同时要确保在打开一个数据库连接前已经完成了初始化。

PQinitSSL

        允许应用选择要初始化哪个安全性库。

void PQinitSSL(int do_ssl);

        这个函数等效于PQinitOpenSSL(do_ssl, do_ssl)。这对于要么初始化OpenSSL以及libcrypto要么都不初始化的应用足够用了。

    PQinitSSL从PostgreSQL 8.0 就存在了,而PQinitOpenSSL直到PostgreSQL 8.4 才被加入,因此PQinitSSL可能对那些需要与旧版本libpq一起工作的应用来说更合适。

十九、在线程化程序中的行为

libpq默认是可再入的并且是线程安全的。你可能需要使用特殊的编译器命令行选项来编译你的应用代码。参考你的系统文档来了解如何编译启用线程的应用,或者在src/Makefile.global中查找PTHREAD_CFLAGSPTHREAD_LIBS。这个函数允许查询libpq的线程安全状态:

PQisthreadsafe

        返回libpq库的线程安全状态。

int PQisthreadsafe();

        如果libpq是线程安全的则返回 1,否则返回 0。

一个线程限制是不允许两个线程同时尝试操纵同一个PGconn对象。特别是你不能从不同的线程通过同一个连接对象发出并发的命令(如果你需要运行并发命令,请使用多个连接)。

PGresult对象在创建后通常是只读的,并且因此可以在线程之间自由地被传递。但是,如果你使用任何第十一节第十三节中描述的PGresult修改函数,你需要负责避免在同一个PGresult上的并发操作。

被废弃的函数PQrequestCancel以及PQoidStatus不时线程安全的并且不应当在多线程程序中使用。PQrequestCancel可以被替换为PQcancelPQoidStatus可以被替换为PQoidValue

如果你在应用中使用 Kerberos (除了在libpq中之外),你将需要对 Kerberos 调用加锁,因为 Kerberos 函数不是线程安全的。参考libpq源代码中的PQregisterThreadLock函数,那里有在libpq和应用之间做合作锁定的方法。

如果你在线程化应用中碰到问题,将该程序运行在src/tools/thread来查看是否你的平台有线程不安全的函数。这个程序会被configure运行,但是对于二进制发布,你的库可能不匹配用来编译二进制的库。

二十、编译 libpq 程序

要编译(即编译并且链接)一个使用libpq的程序,你需要做下列所有的事情:

  • 包括libpq-fe.h头文件:
#include <libpq-fe.h>

如果你无法这样做,那么你通常会从你的编译器得到像这样的错误消息:

foo.c: In function `main':
foo.c:34: `PGconn' undeclared (first use in this function)
foo.c:35: `PGresult' undeclared (first use in this function)
foo.c:54: `CONNECTION_BAD' undeclared (first use in this function)
foo.c:68: `PGRES_COMMAND_OK' undeclared (first use in this function)
foo.c:95: `PGRES_TUPLES_OK' undeclared (first use in this function)
  • 通过为你的编译器提供-Idirectory选项,向你的编译器指出PostgreSQL头文件安装在哪里(在某些情况下编译器默认将查看该目录,因此你可以忽略这个选项)。例如你的编译命令行可能看起来像:
cc -c -I/usr/local/pgsql/include testprog.c

如果你在使用 makefile,那么把该选项加到CPPFLAGS变量中:

CPPFLAGS += -I/usr/local/pgsql/include

如果你的程序可能由其他用户编译,那么你不应该像那样硬编码目录位置。你可以运行工具pg_config在本地系统上找出头文件在哪里:

$ pg_config --includedir
/usr/local/include

如果你安装了pkg-config,你可以运行:

$ pkg-config --cflags libpq
-I/usr/local/include

注意这将在路径前面包括-I

无法为编译器指定正确的选项将导致一个错误消息,例如:

testlibpq.c:8:22: libpq-fe.h: No such file or directory
  • 当链接最终的程序时,指定选项-lpq,这样libpq库会被编译进去,也可以用选项-Ldirectory向编译器指出libpq库所在的位置(再次,编译器将默认搜索某些目录)。为了最大的可移植性,将-L选项放在-lpq选项前面。例如:
cc -o testprog testprog1.o testprog2.o -L/usr/local/pgsql/lib -lpq

你也可以使用pg_config找出库目录:

$ pg_config --libdir
/usr/local/pgsql/lib

或者再次使用pkg-config

$ pkg-config --libs libpq
-L/usr/local/pgsql/lib -lpq

再次提示这会打印出全部的选项,而不仅仅是路径。

指出这一部分问题的错误消息可能看起来像:

testlibpq.o: In function `main':
testlibpq.o(.text+0x60): undefined reference to `PQsetdbLogin'
testlibpq.o(.text+0x71): undefined reference to `PQstatus'
testlibpq.o(.text+0xa4): undefined reference to `PQerrorMessage'

This means you forgot -lpq.

/usr/bin/ld: cannot find -lpq
  • 这意味着你忘记了-L选项或者没有指定正确的目录。

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值