PostgreSQL数据库学习手册之libpq - C 库--介绍(转)[@more@]
Chapter 1. libpq - C 库
Table of Contents
1.1. 介绍
1.2. 数据库联接函数
1.3. 命令执行函数
1.3.1. 主过程
1.3.2. 为包含在 SQL 查询中逃逸字串
1.3.3. 逃逸包含在 SQL 查询中的二进制字串
1.3.4. 检索 SELECT 的结果信息
1.3.5. 检索 SELECT 结果数值
1.3.6. 检索非-SELECT 结果信息
1.4. 异步查询处理
1.5. 捷径接口
1.6. 异步通知
1.7. 与 COPY 命令相关的函数
1.8. libpq 跟踪函数
1.9. libpq 控制函数
1.10. 环境变量
1.11. 文件
1.12. 线程特性
1.13. 制作 Libpq 程序
1.14. 例子程序
1.1. 介绍
libpq 是 PostgreSQL的 C 应用程序员的接口. libpq 是一套允许客户程序向 PostgreSQL 后端服务进程发送查询并且获得查询返回的库过程. libpq 同时也是其他几个 PostgreSQL应用接口下面的引擎, 包括 libpq++ (C++), libpgtcl(Tcl),Perl,和 ecpg.所以如果你使用这些软件包, libpq某些方面的特性会对你非常重要.
本节末尾有三个小程序显示如何利用 libpq书写程序. 在下面目录里面有几个完整的 libpq 应用的例子:
src/test/examples
src/bin/psql
使用 libpq 的前端程序必须包括头文件 libpq-fe.h 并且必须与 libpq 库链接.
PostgreSQL数据库学习手册之libpq-C库---数据库联接函数(转)[@more@]
1.2. 数据库联接函数
下面的过程处理与 PostgreSQL 后端服务器联接的事情. 一个应用程序一次可以与多个后端建立联接.(这么做的原因之一是访问多于一个数据库.) 每个连接都是用一个从PQconnectdb()或 PQsetdbLogin() 获得的PGconn对象表示. 注意,这些函数总是返回一个非空的对象指针,除非存储器少得连个PGconn对象都分配不出来. 在把查询发送给联接对象之前,可以调用PQstatus 函数来检查一下联接是否成功.
*
PQconnectdb 与后端数据库服务器建立一个新的联接.
PGconn *PQconnectdb(const char *conninfo)
这个过程用从一个字符串 conninfo 来的参数与数据库打开一个新的联接.与下面的PQsetdbLogin()不同的是, 我们可以不必更换函数签名(名字)就可以扩展参数集, 所以我们建议应用程序中使用这个函数或者是它的非阻塞的相似函数 PQconnectStart 和 PQconnectPoll. 传入的参数可以为空,表明使用所有缺省的参数,或者可以包含一个或更多个用空白间隔的参数设置.
每个参数以 关键字 = 数值的形式设置. (要写一个空值或者一个包含空白的值, 你可以用一对单引号包围它们,例如,keyword = 'a value' . 数值内部的单引号和反斜扛必须用一个反斜扛逃逸, 也就是说, '或.) 等号周围的空白是可选的.目前可识别的参数键字是:
host
要联接的主机(host ). 如果主机名以斜扛开头, 则它声明使用 Unix 域套接字通讯而不是 TCP/IP 通讯; 该值就是套接字文件所存储的目录.缺省时是与位于 /tmp 里面的 Unix-域套接字联接.
hostaddr
与之联接的主机的 IP 地址。这个可以是标准的数字-点的形式, 象在 BSD 函数inet_aton等里面用的那样。如果声明了一个非零长的字符串,那么使用 TCP/IP 通讯机制。
使用hostaddr取代host 可以让应用避免一次主机名查找, 这一点对于那些有时间约束的应用来说可能是非常重要的。 不过,Kerberos认证系统要求主机(host)名。因此,应用下面的规则。 如果声明了不带hostaddr的 host 那么就强制进行主机名查找。 如果声明中没有 host,hostaddr 的值给出远端的地址; 如果使用了 Kerberos, 将导致一次反向名字查询。如果同时声明了 host 和hostaddr, 除非使用了 Kerberos,否则将使用hostaddr的值作为远端地址; host 的值将被忽略,如果使用了 Kerberos,host 的值用于Kerberos 认证。 要注意如果传递给libpq的主机名(host)不是地址hostaddr处的机器名,那么认证很有可能失败。
如果主机名(host)和主机地址都没有, 那么libpq将使用一个本地的 Unix 域套接字进行通讯。
port
主机服务器的端口号,或者在 Unix 域套接字联接时的套接字扩展文件名.
dbname
数据库名.
user
要联接的用户名。
password
如果后端要求口令认证,所用的口令.
connect_timeout
给连接过程设置的时间范围,以秒计。零或者不设置表示无穷。
options
发给后端的跟踪/调试选项.
tty
文件或控制台(tty),用于从后端的可选调试输出.
requiressl
设为 '1' 要求与后端进行SSL 联接.如果服务器不支持 SSL, 那么Libpq将马上拒绝联接.设置为 '0' (缺省)与服务器进行协商.
如果有任何没有声明的参数,那么将检查对应的环境变量(参阅Section 1.10小节)。 如果环境变量也没有设置,那么使用编译时的硬代码。 返回的值是一个指向代表与后端联接的抽象struct指针。
*
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 前身, 它有固定个数的参数,但是有相同的功能。
*
PQsetdb 与后端数据库服务器建立一个新的联接.
PGconn *PQsetdb(char *pghost,
char *pgport,
char *pgoptions,
char *pgtty,
char *dbName)
这是一个调用 PQsetdbLogin() 的宏,只是login和pwd参数用空(null )代替.提供这个函数主要是为了与老版本的程序兼容.
*
PQconnectStart, PQconnectPoll 与数据库服务器建立一次非阻塞的联接。
PGconn *PQconnectStart(const char*conninfo)
PostgreSQLPollingStatusTypePQconnectPoll(PGconn *conn)
这两个过程用于打开一个与数据库服务器之间的非阻塞的联接: 你的应用的执行线索在执行它的时候不会因远端的 I/O 而阻塞。
数据库联接是用从 conninfo字符串里取得的参数传递给 PQconnectStart进行的。 这个字符串的格式与上面PQconnectdb里描述的一样。
PQconnectStart 和PQconnectPoll 都不会阻塞(进程),不过有一些条件:
o
必须正确提供hostaddr和host参数以确保不会发生正向或者反向的名字查找。 参阅上面PQconnectdb里的这些参数的文档获取细节。
o
如果你调用了PQtrace, 确保你跟踪进入的流对象不会阻塞。
o
你必须在调用PQconnectPoll之前确保 socket 处于正确的状态,象下面描述的那样。
要开始(联接),调用conn=PQconnectStart("connection_info_string") . 如果conn是NULL, 表明libpq无法分配一个新的 PGconn 结构。 否则,返回一个有效的PGconn指针 (尽管还不一定代表一个与数据库有效联接)。 PQconnectStart 一返回,调用 status=PQstatus(conn)。如果 status 等于CONNECTION_BAD, PQconnectStart 失败。
如果PQconnectStart成功了,下一个阶段是轮询 libpq, 这样它就可以继续进行后继的联接动作。 象这样循环:认为一个联接缺省时是"不活跃"的。 如果PQconnectPoll的最后一个返回是 PGRES_POLLING_ACTIVE,则认为它是"活跃的"。 如果PQconnectPoll(conn)的最后一个返回是 PGRES_POLLING_READING,执行一个对 PQsocket(conn)的读select()。 如果最后一个返回是PGRES_POLLING_WRITING,执行一个对PQsocket(conn)的写select()。 如果还要调用PQconnectPoll, 也就是调用完PQconnectStart之后,把它当作最后返回PGRES_POLLING_WRITING那样对待。 如果select()显示 socket 已经准备好, 那么认为它是"活跃的"。如果认为一个联接是 "活跃的", 再次调用PQconnectPoll(conn)。 如果这次调用返回PGRES_POLLING_FAILED, 联接过程失败,如果这次调用 返回PGRES_POLLING_OK,联接成功。
要注意上面用select()来确保一个 socket 准备好只是一个(近似)的例子; 还可以用其他方法,比如一个poll()调用,来代替 select。
在联接的任意时刻,我们都可以通过调用PQstatus 来检查联接的状态。如果这是CONNECTION_BAD, 那么联接过程失败;如果是CONNECTION_OK,那么联接已经做好。 这两种状态同样也可以从上面的PQconnectPoll 的返回值里检测到。 其他状态可能(也只能)在一次异步联接过程中看到。这些标识联接过程 的当前状态,因而可能对给用户提供反馈有帮助。这些状态可能包括:
CONNECTION_STARTED
等待进行联接。
CONNECTION_MADE
联接成功;等待发送。
CONNECTION_AWAITING_RESPONSE
等待来自服务器的响应。
CONNECTION_AUTH_OK
已收到认证;等待联接启动继续进行。
CONNECTION_SETENV
协商环境(联接启动的一部分)。
注意,尽管这些常量将保持下去(为了维持兼容性), 应用决不应该依赖于这些常量的某种特定顺序, 或者是根本不应依赖于这些常量,或者是不应该依赖于这些状态总是某个文档声明的值。 一个应用可能象象下面这样:
switch(PQstatus(conn))
{
case CONNECTION_STARTED:
feedback = "Connecting...";
break;
case CONNECTION_MADE:
feedback = "Connected toserver...";
break;
.
.
.
default:
feedback = "Connecting...";
}
要注意如果PQconnectStart返回一个非空的指针, 你必须在使用完它(指针)之后调用PQfinish, 以处理那些结构和所有相关的存储块。 甚至调用PQconnectStart或者 PQconnectPoll失败时也要这样处理。
如果编译libpq时定义了USE_SSL那么目前的PQconnectPoll将阻塞住。 这个限制可能在将来的版本移除。
这些函数把 socket 置于一个非阻塞的状态,就好象调用了 PQsetnonblocking 一样。
*
PQconndefaults 返回缺省的联接选项。
PQconninfoOption *PQconndefaults(void)
struct PQconninfoOption
{
char *keyword; /* The keyword of the option*/
char *envvar; /* Fallback environmentvariable name */
char *compiled; /* Fallback compiled indefault value */
char *val; /* Option's current value, orNULL */
char *label; /* Label for field in connectdialog */
char *dispchar; /* Character to display forthis field
in a connect dialog. Values are:
"" Display entered value as is
"*" Password field - hide value
"D" Debug option - don't show bydefault */
int dispsize; /* Field size in charactersfor dialog */
}
返回联接选项结构的地址. 可以用于获取所有可能的PQconnectdb选项和它们的当前缺省值.返回值指向一个PQconninfoOption struct 的数组, 该数组以一个有 NULL 关键字指针的条目结束.注意缺省值(val 域)将 依赖于环境变量和其他上下文.调用者必须把联接选项当作只读对待.
在处理完选项数组后,把数组交给PQconninfoFree()释放.如果没有这么做,每次调用PQconndefaults() 都会有一小部分内存泄漏.
在PostgreSQL7.0 以前的版本, PQconndefaults() 返回一个指向静态数组的指针, 而不是一个动态分配的数组.这样做是线程不安全的,因此这个特点被修改了.
*
PQfinish 关闭与后端的联接.同时释放被PGconn 对象使用的存储器.
void PQfinish(PGconn *conn)
注意,即使与后端的联接尝试失败(可由PQstatus判断), 应用也要调用PQfinish释放被PGconn 对象使用的存储器.不应该在调用 PQfinish后再使用PGconn 指针.
*
PQreset 重置与后端的通讯端口.
void PQreset(PGconn *conn)
此函数将关闭与后端的联接并且试图与同一个服务器重建新的联接, 使用所有前面使用过的参数.这在 失去工作联接后进行故障恢复时很有用.
*
PQresetStart PQresetPoll 以非阻塞模式重置与后端的通讯端口。
int PQresetStart(PGconn *conn);
PostgreSQLPollingStatusTypePQresetPoll(PGconn *conn);
此函数将关闭与后端的联接并且试图与同一个服务器重建新的联接, 使用所有前面使用过的参数.这在 失去工作联接后进行故障恢复时很有用.它们和上面的PQreset的区别是它们工作在非阻塞模式。 这些函数的使用有与上面PQconnectStart和 PQconnectPoll一样的限制。
调用PQresetStart。如果它返回 0,那么重置失败。 如果返回 1,用与使用PQconnectPoll 建立联接的同样的 方法使用PQresetPoll重置联接。
libpq应用程序员应该仔细维护PGconn结构.使用下面的访问函数来获取PGconn的内容.避免直接引用PGconn结构里的字段,因为这些字段在今后可能被改变.(从 PostgreSQL 版本 6.4 开始, struct PGconn的定义甚至没有放在 libpq-fe.h里.如果你有一些直接访问PGconn数据域的旧代码,你可以通过包含 libpq-int.h 来访问它们,但我们鼓励你赶快修改那些代码.)
*
PQdb 返回联接的数据库名.
char *PQdb(const PGconn *conn)
PQdb 和下面几个函数返回联接时建立起来的几个值.这些值在PGconn对象的生存期内是固定的.
*
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 ).
char *PQtty(const PGconn *conn)
*
PQoptions 返回联接中使用的后端选项.
char *PQoptions(const PGconn *conn)
*
PQstatus 返回联接的状态.
ConnStatusType PQstatus(const PGconn *conn)
这个状态可以是一些值之一。 不过,如果不是一次异步联接过程的话,我们只能看到其中的两个 - CONNECTION_OK 或 CONNECTION_BAD。一个与数据库的成功的联接返回状态 CONNECTION_OK。 一次失败的企图用状态 CONNECTION_BAD 标识。通常,一个 OK 状态保持到 PQfinish,但是一个通讯失败可能会导致状态过早地改变为 CONNECTION_BAD 。这时应用可以试着调用 PQreset 来恢复.
参阅PQconnectStart和PQconnectPoll 条目看看可能出现的其他的状态码。
*
PQerrorMessage 返回联接中操作产生的最近的错误信息.
char *PQerrorMessage(const PGconn* conn);
几乎所有libpq函数在失败时都会设置 PQerrorMessage.注意libpq的传统是, 一个非空的 PQerrorMessage 将在结尾包含一个新行.
*
PQbackendPID 返回控制此联接的后端服务器的进程号ID。
int PQbackendPID(const PGconn *conn);
这个后端PID 在调试和对比NOTIFY 信息( 包含发出通知的后端的 PID )时很有用. 注意该 PID 属于运行数据库服务器的主机的进程, 而不是本地主机!
*
PQgetssl 返回联接使用的 SSL 结构,或者如果 SSL 没有使用的话返回 NULL.
SSL *PQgetssl(const PGconn *conn);
这个结构可以用于核实加密级别,检查服务器认证等信息.参考 SSL 文档获取关于这个结构的更多信息.
为了获取这个函数的原形,你必须定义 USE_SSL. 这样做会自动包含来自OpenSSL的 ssl.h.
PostgreSQL数据库学习手册之libpq-C库----命令执行函数(转)[@more@]
1.3. 命令执行函数
一旦与数据库服务器的联接成功建立,便可用这里描述的函数执行 SQL 查询和命令。
1.3.1. 主过程
*
PQexec 给服务器提交一条命令并且等待结果.
PGresult *PQexec(PGconn *conn,
const char *query);
返回一个PGresult指针或者也可能是一个 NULL 指针. 通常返回一个非空(non-NULL)的指针, 除非没有内存或发生了象不能把命令发送到后端这样的严重错误.如果返回的是 NULL,它应该被当作PGRES_FATAL_ERROR结果处理.用PQerrorMessage获取有关错误的更多信息.
PGresult 结构封装了后端返回的结果. libpq 应用程序员应该仔细维护PGresult抽象.用下面的访问函数来获取PGresult的内容.避免直接引用 PGresult结构的数据域,因为这个结构可能会在未来被改变.(从 PostgreSQL 版本 6.4 开始, struct PGresult的定义甚至都没有放在 libpq-fe.h里.如果你有一些直接访问 PGresult数据域的老代码,你可以通过包含libpq-int.h继续使用它们,但是我们鼓励你立刻修改代码.)
*
PQresultStatus 返回命令的结果状态.
ExecStatusType PQresultStatus(constPGresult *res)
PQresultStatus可以返回下面数值之一:
o
PGRES_EMPTY_QUERY -- 发送给后端的字串是空的
o
PGRES_COMMAND_OK -- 成功完成一个没有返回数据的命令
o
PGRES_TUPLES_OK -- 成功执行查询
o
PGRES_COPY_OUT -- (从服务器)Copy Out (拷贝出)数据传输开始
o
PGRES_COPY_IN -- Copy In (拷贝入)(到服务器)数据传输开始
o
PGRES_BAD_RESPONSE -- 服务器的响应无法理解
o
PGRES_NONFATAL_ERROR
o
PGRES_FATAL_ERROR
如果结果状态是 PGRES_TUPLES_OK ,那么可以用下面的过程从查询的返回中抽取元组信息.注意一个碰巧检索了零条元组的 SELECT 仍然显示 PGRES_TUPLES_OK。 PGRES_COMMAND_OK用于不返回元组的命令(INSERT,UPDATE,等)。返回 PGRES_EMPTY_QUERY 的响应通常意味着客户端软件里面的臭虫。
*
PQresStatus 把PQresultStatus返回的枚举类型转换成一个描述状态码的字符串常量。
char *PQresStatus(ExecStatusType status);
*
PQresultErrorMessage 返回与查询关联的错误信息,或在没有错误时返回一个空字符串.
char *PQresultErrorMessage(const PGresult*res);
紧跟在一个 PQexec 或PQgetResult 调用后面,PQerrorMessage (对联接)将返回与 PQresultErrorMessage (对结果)一样的字符串.不过,一个PGresult将保有其错误信息直到被删除,而连结的错误信息将在后续的操作完成时被改变.当你想知道与某个 PGresult相关联的状态时用 PQresultErrorMessage ;当你想知道与联接的最近一个操作相关联的状态时用 PQerrorMessage;
*
PQclear 释放于PGresult相关联的存储空间. 任何不再需要的查询结果在不需要的时候都应该用PQclear释放掉.
void PQclear(PQresult *res);
你可以保留PGresult对象任意长的时间; 当你提交新的查询时它并不消失,甚至你断开联接后也是这样.要删除它,你必须调用 PQclear.不这么做将导致前端的存储器泄漏.
*
PQmakeEmptyPGresult 构造一个给出状态的为空的PGresult对象.
PGresult* PQmakeEmptyPGresult(PGconn *conn,ExecStatusType status);
这是libpq的内部过程, 用于分配和初始化一个空PGresult对象. 它被输出是因为一些应用需要自行生成结 果对象(尤其是特定的带有错误状态的对象). 如果conn非空(NULL)并且状态指示一个错误, 联接当前的错误信息被拷贝到PGresult. 注意最终对该对象要调用PQclear, 正如libpq本身返回的PGresult一样.
1.3.2. 为包含在 SQL 查询中逃逸字串
PQescapeString 为在 SQL 查询中使用逃逸一个字串.
size_t PQescapeString (char *to, const char*from, size_t length);
如果你需要在查询字串中包含一个从不可靠的源头接收的字串 (比如,它们是随机用户输入的),那么,出于安全原因,你不能把它们直接包含在 SQL 查询里.你应该把特殊字符引起来,否则它们就会被 SQL 分析器代换.
PQescapeString 执行这个操作.from 指向将要逃逸的字串的第一个字符,length 参数计算在这个字串里的字符数量(字串结尾的字节零不是必须的,也不计入长度). to 应该指向一个缓冲区,这个缓冲区至少能保存 length 数值的两倍还多一个的字符,否则该函数行为将不可预测.调用PQescapeString 就会把逃逸的 from 字串转换到 to 缓冲区,把特殊字符替换掉以免发生意外,并且追加终止的字节零.那些必须包围在PostgreSQL字串文本周围的单引号不算结果字串的一部分.
PQescapeString 返回写到 to 里面的字符数目,不包括结尾的字节零.如果 to 和 from 字串相互重叠,那么其行为不可预测.
1.3.3. 逃逸包含在 SQL 查询中的二进制字串
PQescapeBytea 逃逸那些在 SQL 查询中使用二进制字串(bytea 类型).
unsigned char *PQescapeBytea(const unsignedchar *from,
size_t from_length,
size_t *to_length);
在 SQL 语句中用做BYTEA 字串文本的 一部分的时候, 有些 ASCII 字符必需被逃逸 (但是对于所有字符而言是可以逃逸). 通常,要逃逸一个字符,它是被转换成一个三位八进制数字,该数字数值等于相应的十进制 ASCII 数值,然后前缀 两个反斜扛.单引号(')和反斜扛字符()有自己特殊的逃逸序列.参阅 用户手册获取更多信息. PQescapeBytea 执行这个操作,它只逃逸需要逃逸的最少的字符.
from 参数指向需要逃逸的字串的第一个字符, from_length 参数反映在这个二进制字串 (那种字节零既不必要也不计算在内的字串)里字符的个数. to_length 参数应该是一个指向某个缓冲区的指针,它的空间应该能够保存逃逸后的结果字串长度. 结果字串长度不包括结果结尾的字节零.
PQescapeBytea 返回一个 from 参数的二进制字串的逃逸后的版本,返回给调用者提供的缓冲区.返回的字串已经把所有特殊的字符替换调了,这样他们就可以由 PostgreSQL的字串文本分析器以及 bytea 的输入函数 正确地处理.同时还追加了一个结尾的字节零.那些必需包围在 PostgreSQL字串文本周围的单引号不算结果字串的一部分.
PQunescapeBytea 把一个二进制数据的逃逸后的字串表现形式转换成二进制数据 -PQescapeBytea 的反作用.
unsigned char *PQunescapeBytea(constunsigned char *from, size_t *to_length);
from 参数指向一个逃逸后的字串,比如 PQgetvalue 从一个 BYTEA 字段返回的.PQunescapeBytea 把 它的字串表现形式转换成二进制形式,填充到一个缓冲区. 它返回一个指向该缓冲区的指针,若为 NULL 则出错, 缓冲区的尺寸放在 to_length 里. 该指针随后可以用做 free(3) 的参数.
1.3.4. 检索 SELECT 的结果信息
*
PQntuples 返回查询结果里的元组(元组)个数.
int PQntuples(const PGresult *res);
*
PQnfields 返回查询结果里每个元组的数据域(字段)的个数.
int PQnfields(const PGresult *res);
*
PQfname 返回与给出的数据域编号相关联的数据域(字段)的名称.数据域编号从 0 开始
char *PQfname(const PGresult *res,
int field_index);
*
PQfnumber 返回与给出的数据域名称相关联的数据域(字段)的编号.
int PQfnumber(const PGresult *res,
const char *field_name);
如果给出的名字不匹配任何域,返回-1.
*
PQftype 返回与给定数据域编号关联的数据域类型.整数返回值是一个该类型的内部编码.数据域编号从0 开始.
Oid PQftype(const PGresult *res,
int field_index);
你可以查询系统表 pg_type 以获取各种数据类型的名称和属性。内建的数据类型的 OID 在源码树的 src/include/catalog/pg_type.h 文件里定义。
*
PQfmod 返回与给定数据域编号相关联的类型相关的修正数据(??). 数据域编号从 0 开始.
int PQfmod(const PGresult *res,
int field_index);
*
PQfsize 返回一个PGresult 里面的一条元组的单独的一个数据域(字段)的值. 元组和数据域编号从0 开始.
int PQfsize(const PGresult *res,
int field_index);
PQfsize返回在数据库元组里面给该数据域分配的空间,换句话说就是该数据类型在服务器里的二进制形式的大小(尺寸). 如果该数据域是可变尺寸,返回 -1.
*
PQbinaryTuples 如果PGresult包含二进制元组数据时返回 1, 如果包含 ASCII 数据返回0.
int PQbinaryTuples(const PGresult *res);
目前,二进制元组数据只能从一个二进制游标里抽取数据的查询返回.
1.3.5. 检索 SELECT 结果数值
*
PQgetvalue 返回一个PGresult 里面一个元组单独的一个数据域(字段)的值. 元组和数据域编号从 0 开始.
char* PQgetvalue(const PGresult *res,
int tup_num,
int field_num);
对大多数查询而言, PQgetvalue 返回的值是一个表示字段值的空(NULL)结尾的字符串.但是如果 PQbinaryTuples() 为 1, PQgetvalue 返回的值就是该类型在后端服务器内部的二进制表现形式(但是不包括尺寸字--如果数据域是变长的).这样,把数据转换成对应的 C 类型就是程序员的责任了. PQgetvalue 返回的指针指向一个本身是 PGresult结构的一部分的存储区域.我们不能更改它,并且如果 我们要在PGresult结构的生存期后还要使用它的话,我们必须明确地把该数值拷贝到其他存储器中.
*
PQgetisnull 测试一个数据域是否为空(NULL).元组和数据域编号从 0 开始.
int PQgetisnull(const PGresult *res,
int tup_num,
int field_num);
如果该域包含 NULL,函数返回1,如果包含非空(non-null )值,返回 0.(注意,对一个 NULL 数据域,PQgetvalue 将返回一个空字符串,不是一个空指针.)
*
PQgetlength 返回以字节计的数据域(字段)的长度.元组和数据域编号从 0 开始.
int PQgetlength(const PGresult *res,
int tup_num,
int field_num);
这是某一特定数据值的实际数据长度,也就是由PQgetvalue 指向的对象的尺寸.注意,对于 ASCII 表示的数值,这个尺寸与PQfsize 报告的二进制尺寸无关.
*
PQprint 向指定的输出流打印所有的元组和(可选的)字段名称.
void PQprint(FILE* fout, /* output stream*/
const PGresult *res,
const PQprintOpt *po);
struct {
pqbool header; /* print output fieldheadings and row count */
pqbool align; /* fill align the fields */
pqbool standard; /* old brain dead format*/
pqbool html3; /* output html tables */
pqbool expanded; /* expand tables */
pqbool pager; /* use pager for output ifneeded */
char *fieldSep; /* field separator */
char *tableOpt; /* insert to HTML table ...*/
char *caption; /* HTML caption */
char **fieldName; /* null terminated arrayof replacement field names */
} PQprintOpt;
这个函数以前被 psql 用于打印查询结果,但是现在已经不用这个函数了,并且此函数不再有活跃的支持。
1.3.6. 检索非-SELECT 结果信息
*
PQcmdStatus 返回产生PGresult的 SQL 命令的命令状态字符串.
char * PQcmdStatus(PGresult *res);
*
PQcmdTuples 返回被 SQL 命令影响的行的数量.
char * PQcmdTuples(PGresult *res);
如果产生PGresult的SQL命令是 INSERT, UPDATE 或 DELETE, 这里返回涉及行的行数.如果是其他命令返回一个空字符串.
*
PQoidValue 返回一个插入的元组的对象标识(OID)——如果 SQL 命令是 INSERT.否则,返回 InvalidOid.
Oid PQoidValue(const PGresult *res);
如果你包含了 libpq头文件,那么 Oid 和常量 InvalidOid 的类型将被定义。他们都是某种整型类型。
*
PQoidStatus 返回一个被插入的元组的对象标识的字串, 如果 SQL 命令是INSERT。 否则. 返回一个空字串。
char * PQoidStatus(const PGresult *res);
因为有了 PQoidValue ,我们不建议使用这个函数,而且它在线程里使用也是不安全的
PostgreSQL数据库学习手册之libpq-C库---异步查询处理(转)[@more@]
1.4. 异步查询处理
PQexec 函数对简单的同步应用里提交命令已经是足够用的了.但是它却有几个主要的缺陷:
*
PQexec 等待命令结束.应用可能有其他工作要做(例如维护用户界面),这时它可不希望阻塞在这里等待返回.
*
因为控制是藏在 PQexec内部,前端很难取消掉正进行着的命令.(可以通过信号控制器进行,但没有别的方法.)
*
PQexec 只能返回一个PGresult结构.如果提交的命令字符串包含多个 SQL 命令,除了最后一个PGresult以外都会被 PQexec 丢弃。
不想受到这些限制的应用可以改用下面的函数,这些函数也是构造 PQexec 的函数: PQsendQuery 和 PQgetResult。
使用这些(异步)功能以及 PQputline 和 PQputnbytes 的老一些的程序可能在等待数据发送给后端时阻塞住,为解决这样的问题,增加了函数 PQsetnonblocking.
旧应用可以忽略 PQsetnonblocking 的使用,维持原有的阻塞特征。新的程序可以利用 PQsetnonblocking 获得与后端完全非阻塞的联接。
*
PQsetnonblocking 把该联接的状态设置为非阻塞。
int PQsetnonblocking(PGconn *conn, int arg)
如果arg为 TRUE,把联接状态设置为非阻塞,如果arg为 FALSE, 把联接状态设置为阻塞.如果 OK 返回 0,如果错误返回 -1.此函数将确保对
在非阻塞状态,调用 PQputline,PQputnbytes, PQsendQuery 和 PQendcopy 的时候不被阻塞, 而是如果需要再次它们时将是返回一个错误(而不是阻塞)。
当把一个数据库的联接设置为非阻塞的模式并且调用了 PQexec,它将暂时把联接状态设置为阻塞模式直到 PQexec 完成。
在不久的将来将有更多的libpq会设计成在 PQsetnonblocking 方式下是安全的。
*
PQisnonblocking 返回数据库联接的阻塞状态。
int PQisnonblocking(const PGconn *conn)
如果联接设置为非阻塞状态,返回 1,如果是阻塞状态返回 0。
*
PQsendQuery 向服务器提交一个命令而不等待结果. 如果查询成功发送则返回 1,否则返回 0.此时,可以用PQerrorMessage 获取关于失败的信息).
int PQsendQuery(PGconn *conn,
const char *query);
在成功调用 PQsendQuery后,调用 PQgetResult 一次或者多次获取结果. 可以不再调用PQsendQuery (在同一次联接里)直到 PQgetResult 返回 NULL,表明命令完成.
*
PQgetResult 等待从前面 PQsendQuery 调用返回的下一个结果, 然后返回之.当查询结束并且没有更多结果后返回 NULL.
PGresult *PQgetResult(PGconn *conn);
必须重复的调用 PQgetResult ,直到它返回 NULL, 表明该命令结束.(如果在没有活跃的命令时调用, PQgetResult 将只是立即返回 NULL.) 每个 PQgetResult 返回的非 NULL 结果都应该用前面 描述的 PGresult 访问函数进行分析.不要忘了在结束分析后用 PQclear 释放每个结果对象. 注意,PQgetResult 只是在有查询激活而且必须的返回数据还没有被 PQconsumeInput 读取时阻塞.
使用 PQsendQuery 和PQgetResult 解决了 PQexec的一个问题:如果一个命令字符串包含多个 SQL 命令,这些命令的结果可以独立的获得.(顺便说一句:这样就允许一种简单的重叠处理模式,前端可以处理一个查询的结果而后端可以仍然在处理同一命令字符串的后面的查询.)但是,调用 PQgetResult 将仍然导致前端被阻塞住直到后端完成下一个 SQL 命令.这一点可以通过合理的使用下面三个函数来避免:
*
PQconsumeInput 如果存在后端来的输入可用,则使用之.
int PQconsumeInput(PGconn *conn);
PQconsumeInput 通常返回 1 表明"没有错误",而返回 0 表明有某种错误发生, (同时设置 PQerrorMessage).注意这个结果并不表明实际上是否收集了数据.在调用 PQconsumeInput之后,应用可以检查 PQisBusy 和/或 PQnotifies 看一眼它们的状态是否改变.
PQconsumeInput 可以在应用还没有做好处理结果或通知的情况下被调用.这个过程将读取可用的数据并且在一个缓冲区里保存它,这样导致一个 select() 读准备好标识的生成.这样应用就可以使用 PQconsumeInput立即清掉 select() 条件,然后在空闲的时候检查结果.
*
PQisBusy 在查询忙的时候返回 1 ,也就是说, PQgetResult 将阻塞住等待输入.一个 0 的返回表明这时调用 PQgetResult 可以确保不阻塞.
int PQisBusy(PGconn *conn);
PQisBusy 本身将不会试图从后端读取数据;所以必须先调用 PQconsumeInput ,否则忙状态将永远不会消除.
*
PQflush 试图把任何正在排队的数据冲刷到后端,如果成功(或者发送队列为空)返回 0,如果因某种原因失败返回EOF。
int PQflush(PGconn *conn);
在一个非阻塞的联接调用 select() 判断是否有响应到达之前需要调用一个 PQflush 。如果返回 0 则保证了与后端的发送队列里面没有待发送的数据。只有使用了 PQsetnonblocking 的应用需要这个。
*
PQsocket 获取用于后端联接套接字的文件描述符号. 一个有效的描述符应该是 >= 0;一个 -1 表明当前没有打开与后端的联接.
int PQsocket(const PGconn *conn);
PQsocket 应该用于获取准备调用 select() 的后端套接字描述符.这就允许一个应用使用阻塞的联接等待后端的响应或者其他条件.如果 select() 的结果表明可以从后端套接字读取数据,那么应该调用PQconsumeInput 读取数据;之后,,PQisBusy,PQgetResult,和/或PQnotifies 可用于处理返回信息.
非阻塞的联接(那些使用了 PQsetnonblocking的联接)在 PQflush 返回 0 之前,(这表明没有数据缓冲着等待发送给后端)不应该使用 select()。
一个使用这些函数的典型的前端将有一个主循环使用 select() 等待所有它必须处理的条件.其中一个条件将会是后端来的数据已准备好,从 select() 的角度来看就是 PQsocket 标识的文件描述符上已经有可读取的数据.当主循环侦测到输入准备好,它将调用PQconsumeInput 读取输入.然后可以调用 PQisBusy 返回 false (0)后面可以跟着 PQgetResult。同样它(用户应用)可以调用 PQnotifies测 NOTIFY 信息(参阅下面的 "异步通知").例子程序节里面给出了一个例子程序.
一个使用 PQsendQuery/PQgetResult 的前端同样也可以试图取消一个正在被后端处理的命令.
*
PQrequestCancel 要求 PostgreSQL 放弃处理当前命令.
int PQrequestCancel(PGconn *conn);
如果取消的请求成功发送,返回值是 1,否则是 0.(如果为假,PQerrorMessage 会告之为什么.)不过,取消请求的成功发送将不保证请求将产生作用.不管 PQrequestCancel 的返回值是什么,应用都必须继续使用 PQgetResult进行通常的后续的结果读取工作.如果取消动作生效,当前的命令将提前退出并返回一个错误结果.如果取消动作失败(也就是后端已经处理完命令了),那么将没有可见的结果.
注意:如果当前的命令是事务的一部分,取消动作将退出整个事务.
PQrequestCancel 可以很安全地从一个信号句柄里调用.所以,如果取消动作可以从信号句柄里发出的话,它也可以与简单的 PQexec一起使用.例如, psql 从一个SIGINT信号句柄里调用 PQrequestCancel ,因此允许交互地取消通过 PQexec 发出的查询.注意,如果没有与后端建立联接或者后端当前没有处理命令,PQrequestCancel 将不发生做用
PostgreSQL数据库手册之libpq-C库-----捷径接口(转)[@more@]
1.5. 捷径接口
PostgreSQL 提供一个发往后端的函数调用的捷径接口.这是一个通向系统内部的后门,因而可能存在安全漏洞.大多数用户应该不需要这个特性.
*
PQfn 请求允许通过捷径接口执行后端函数.
PGresult* PQfn(PGconn* conn,
int fnid,
int *result_buf,
int *result_len,
int result_is_int,
const PQArgBlock *args,
int nargs);
fnid参数是待执行的函数的对象标识(OID). result_buf是放置返回值的缓冲区. 调用者必须为返回值分配足够的空间(这里没有检查!).实际的返回值长度将被放在result_len指向的整数里返回. 如果预期返回值是 4-字节整数,把result_is_int设为 1; 否则设为 0.(把result_is_int设为 1 告诉 libpq必要时交换数值字节序, 这样就可以正确地传输成客户机上的整数值.当 result_is_int是 0 时, 后端发送回来的字节串不做修改.) args和nargs声明要传入函数的参数.
typedef struct {
int len;
int isint;
union {
int *ptr;
int integer;
} u;
} PQArgBlock;
PQfn 总是返回一个有效的PGresult*. 在使用结果之前应该检查结果状态。 当结果不再使用后,调用者有义务使用 PQclear 释放PGresult
PostgreSQL数据库学习手册之libpq-C---库异步通知(转)[@more@]
1.6. 异步通知
PostgreSQL 支持通过LISTEN和NOTIFY命令产生的异步通知.一个后端用LISTEN命令注册一个它感兴趣的通知条件(也可以用UNLISTEN去掉这个注册).所有正在监听某一通知条件的后端在该条件名的NOTIFY(通知)被任何后端执行后都将被异步地通知.通知发出者不会传递附加的信息到监听者.因此,很典型地是,任何实际的需要被传递的数据都是通过一个数据库关系传递的.通常,条件名与相关联的关系同名,但是并不是一定要与某个关系相关才行.
libpq 应用把LISTEN和 UNLISTEN 命令作为通常的 SQL 命令提交.因此,通过调用PQnotifies()可以侦测到 NOTIFY 消息的到达.
*
PQnotifies 从一个来自后端的未处理的通知信息列表中返回下一条通知. 如果没有未处理的信息则返回NULL. 一旦PQnotifies返回一条通知, 该通知会被认为已处理并且将被从通知列表中删除.
PGnotify* PQnotifies(PGconn *conn);
typedef struct pgNotify {
char *relname; /* 包含数据的关系(表)名字*/
int be_pid; /* 后端进程 id*/
} PGnotify;
在处理完 PQnotifies 返回的PGnotify对象后,别忘了用 free() 把它释放,以避免内存泄漏。
注意: 在 PostgreSQL6.4 和更高的版本里, be_pid 是正在通知的后端的PID,而在早些的版本里它总是你自己的后端的 PID。
第二个样本程序给出一个使用异步通知的例子.
PQnotifies() 实际上并不读取后端数据;它只是返回被前面的另一个libpq函数吸收的信息.在以前的 libpq的版本里,周期性的收到通知(NOTIFY)信息的唯一方法是持续的提交查询,即使是空查询也可以,并且在每次 PQexec()后检查 PQnotifies() 。现在这个方法也能还工作,不过我们认为它太浪费处理器时间而废弃了它。
在你没有可用的查询提交时检查 NOTIFY 消息的更好的方法是调用 PQconsumeInput(),然后检查 PQnotifies().你可以使用 select() 来等待后端数据的到达,这样在没有数据可处理时可以不浪费 CPU 时间.(参阅 PQsocket() 获取用于 select() 的文件描述符.)注意这种方法不管你使用 PQsendQuery/PQgetResult 还是简单的 PQexec来执行查询都能工作.不过,你应该记住在每次 PQgetResult 或 PQexec后检查 PQnotifies() ,看看在处理查询的过程中是否有通知到达
1.7. 与 COPY 命令相关的函数
PostgreSQL 里的 COPY 命令里有用于 libpq 里从网络联接读出或者写入的选项.因此,这些函数有必要直接访问网络联接,以便应用可以充分利用这个功能.
这些函数应该只在从 PQexec 或 PQgetResult 获得了 PGRES_COPY_OUT 或 PGRES_COPY_IN 结果对象的情况下执行.
*
PQgetline 读取一个以回车符(换行符)结尾的字符行中指定字节数的字符 (由后端服务器传输)到一个字符串缓冲区.
int PQgetline(PGconn *conn,
char *string,
int length)
类似 fgets,这个过程拷贝最多 length-1 个字符到字符串里.但是它会象 gets那样把结尾的换行符转换成一个字节零. PQgetline 在输入结束时返回 EOF ,如果整行都被读取了返回 0,如果缓冲区填满了而还没有遇到结束的换行符则返回 1.
注意,应用程序必须检查新行是否包含两个字符 ".",这表明后端服务器已经完成了 copy 命令的结果集的发送.如果应用可能收到超过 length-1 字符长的字符,我们就应该确保正确识别"."行(例如,不要把一个长的行的结束当作一个终止行).src/bin/psql/copy.c 里的代码包含正确控制 copy 协议的过程.
*
PQgetlineAsync 不做阻塞地读取一行以换行符结尾的字符(由后端服务器传输)到一个缓冲区中.
int PQgetlineAsync(PGconn *conn,
char *buffer,
int bufsize)
这个过程类似于 PQgetline,但是可以用于那些必须异步地读取COPY 数据的应用,因而是不阻塞的.在使用了 COPY 命令和获取了 PGRES_COPY_OUT响应之后,应用应该调用 PQconsumeInput 和 PQgetlineAsync 直到收到数据结束的信号.不象PQgetline,这个过程负责检测数据结束.在每次调用时,如果libpq 的输入缓冲区内有可用的一个完整的换行符结尾的数据行或者调用者声明的缓冲区小于到来的数据行的长度, PQgetlineAsync 都将返回数据.否则,在其他数据到达之前不会返回数据.
如果见到了拷贝数据结束的信号,此过程返回 -1,如果没有可用数据返回 0,或者是给出一个正数表明返回的数据的字节数.如果返回 -1,调用者下一步必须调用 PQendcopy,然后回到正常处理.返回的数据将不会超出换行符的范围.如果可能,每次将返回一个完整行.但如果调用者提供的缓冲区太小,无法容下后端发出的整行,那么将返回部分行.这个可以通过测试返回的最后一个字节是否 " " 来确认.返回的字符串不是空结尾的.(如果你想得到一个空结尾的字串,确保你传递了一个 bufsize 比实际大小少一字节的缓冲区.)
*
PQputline 向后端服务器发送一个空结尾的字符串.成功时返回 0,如果不能发送字符串返回 EOF.
int PQputline(PGconn *conn,
const char *string);
注意,应用在最后一行时必须明确的发送两个字符 "." ,通知后端它已经完成数据发送.
*
PQputnbytes 向后端服务器发送一个非空结尾的字符串.成功时返回 0,如果不能发送字符串返回 EOF.
int PQputnbytes(PGconn *conn,
const char *buffer,
int nbytes);
此函数类似 PQputline ,除了数据缓冲区不需要是空结尾的之外,因为要发送的字节数是直接声明的.
*
PQendcopy 与后端同步.这个函数等到后端完成拷贝(才返回?).你可以在用 PQputline 向后端发送完最后一个字符串后或者用 PGgetline从后端获取最后一行字符串后调用它.我们必须调用这个函数,否则后端可能会和前端"同步丢失"。在这个函数返回后,后端就已经准备好接收下一个 SQL 命令了.成功时返回0,否则返回非零值.
int PQendcopy(PGconn *conn);
一个例子:
PQexec(conn, "CREATE TABLE foo (aint4, b char(16), d double precision)");
PQexec(conn, "COPY foo FROMSTDIN");
PQputline(conn, "3 hello world 4.5");
PQputline(conn,"4 goodbye world 7.11");
...
PQputline(conn,". ");
PQendcopy(conn);
在使用 PQgetResult时,应用应该对 PGRES_COPY_OUT 的结果做出反应:重复调用 PQgetline ,并且在收到结束行时调用 PQendcopy.然后应该返回到 PQgetResult 循环直到 PQgetResult 返回 NULL.类似地, PGRES_COPY_IN 结果是用一系列 PQputline 调用最后跟着 PQendcopy,然后返回到 PQgetResult 循环.这样的排列将保证嵌入到一系列 SQL 命令里的 copy in 或copy out 命令将被正确执行.
旧的应用大多通过 PQexec 提交一个 copy in 或 copy out 命令并且假设在 PQendcopy 后事务完成.这样只有在copy in/out 是命令字串里的唯一的 SQL 命令时才能正确工作
1.8. libpq 跟踪函数
*
PQtrace 打开对前端/后端通讯的跟踪,把调试信息输出到一个文件流里.
void PQtrace(PGconn *conn
FILE *debug_port)
*
PQuntrace 关闭PQtrace打开的跟踪
void PQuntrace(PGconn *conn)
1.9. libpq 控制函数
*
PQsetNoticeProcessor 控制libpq生成的通知和警告信息的汇报.
typedef void (*PQnoticeProcessor) (void*arg, const char *message);
PQnoticeProcessor
PQsetNoticeProcessor(PGconn *conn,
PQnoticeProcessor proc,
void *arg);
缺省时,libpq 在后端往stderr 上打印“通知”信息和一些它自身生成的错误信息.这个特性可以通过提供一个对信息进行某种加工的回叫函数来更改.我们向这个回叫函数传递错误信息的文本(包括文本结尾的换行符),和一个空(void)指针,该指针与传递给 PQsetNoticeProcessor 的完全一样.(如果需要,这个指针可以用于访问应用相关的状态.)缺省的通知处理器只是简单的
static void
defaultNoticeProcessor(void * arg, constchar * message)
{
fprintf(stderr, "%s", message);
}
要使用特殊的通知处理器,在创建完新的PGconn对象后马上调用 PQsetNoticeProcessor。
返回值是指向以前的通知处理器的指针。如果你提供了一个 NULL 做为回调指针,那么不会发生任何动作,只是返回当前的指针。
一旦你设置了一个通知处理器,你就应该预料到该函数可能在PGconn对象或 PGresult对象从开始存在时起就可能被调用.当创建一个PGresult时,PGconn 的当前的通知指针被拷贝到 PGresult里提供给可能需要的过程,如 PQgetvalue 使用.
1.10. 环境变量
下面的环境变量可以用于选择缺省的联接参数值,这些值将被 PQconnectdb 或 PQsetdbLogin 使用--如果调用代码没有直接声明相应值的话.这些(环境变量)可以避免把麻烦的数据库名强加入简单的应用程序的硬代码里面.
*
PGHOST 设置缺省的服务器名.如果它以一个斜扛开头,那么它声明一个 Unix 域套接字而不是 TCP/IP 通讯;其值就是该套接字文件存储的目录(缺省为 /tmp).
*
PGPORT 设置缺省的 TCP 端口号或者设置与 PostgreSQL 的 Unix 域套接字的文件扩展(文件标识符).
*
PGDATABASE 设置缺省的 PostgreSQL 数据库名.
*
PGUSER 设置用于与数据库联接和用于认证的用户名.
*
PGPASSWORD 如果后端要求口令认证,设置使用的口令.因为安全原因,这个功能已经废弃了;请考虑使用 $HOME/.pgpass文件。
*
PGREALM 设置与 PostgreSQL一起使用的 Kerberos 领域--如果该域与本地域不同的话。如果设置了 PGREALM ,PostgreSQL 应用将试图用这个域(realm)与服务器进行认证并且使用独立的门票文件(ticket files)以避免与本地的门票文件冲突.只有在后端选择了 Kerberos 认证时才使用这个环境变量.(译注:门票文件是 Kerberos认证协议中用于交换密钥的一个文件/服务器。)
*
PGOPTIONS 为 PostgreSQL 后端设置附加的运行时选项.
*
PGTTY 设置后端调试信息显示输出的文件或者控制台(tty).
下面的环境变量可以用于为每个PostgreSQL 会话声明用户级别的缺省特性:
*
PGDATESTYLE 设置缺省的日期/时间表现形式.
*
PGTZ 设置缺省的时区.
*
PGCLIENTENCODING 设置缺省的客户端编码(如果配制PostgreSQL 时选择了多字节支持).
下面的环境变量可以用于为每个PostgreSQL 会话声明缺省的内部特性:
*
PGGEQO 为基因优化器设置缺省模式.
参阅 SET SQL 命令获取这些环境变量的正确值的信息.
1.11. 文件
> 家目录中的.pgpass 是一个可以包含口令的文件。如果连接要求口令,那么可以用它。这个文件的格式应该是:
hostname:port:database:username:password
这些行可以是一个文本名字,或者 *,它匹配所有的东西。第一个匹配的东西将得以使用,因此我们应该把最具体的记录放在前面。有 : 或者 的记录应该用 逃逸。
.pgpass 的权限必须不允许任何全局或者同组的用户访问;我们可以用命令 chmod 0600 .pgaccess 实现这个目的。如果权限比这个松,这个文件将被忽略。
1.12. 线程特性
到 PostgreSQL 7.0 时 libpq 是线程安全的, --只要不是两个线程试图同时操作同一个PGconn对象.实际上,你无法从不同的线程向同一个联接对象发出并发的查询.(如果你需要运行并行查询,请启动多个联接.)
PGresult对象在创建后是只读的,因此可以自由地在线程之间传递.
过时了的函数 PQoidStatus 和 fe_setauthsvc 都是线程不安全的,因此不应该在一个多线程的程序里面使用.PQoidStatus 可以由 PQoidValue代替.而我们觉得根本没有调用 fe_setauthsvc 的必要.
Libpq 客户端使用的 crypt 加密方法倚赖 crypt() 系统函数,它通常不是线程安全的.我们最好使用 MD5 加密,它在所有平台上是线程安全的.
1.13. 制作 Libpq 程序
要制作(也就是说编译和链接)你的 libpq 程序,你需要做下面的一些事情∶
*
包含 libpq-fe.h 头文件∶
#include
如果你没干这件事,那么你通常会看到类似下面这样的来自编译器的信息∶
foo.c: In function `main':
foo.c:34: `PGconn' undeclared (first use inthis function)
foo.c:35: `PGresult' undeclared (first usein 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)
*
告诉你的编译器PostgreSQL头文件的安装位置, 方法是给你的编译器提供 -Idirectory 选项. (有些时候编译器会查找缺省的目录,因此你可以忽略这些选项.) 比如,你的命令行看起来象∶
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
如果没能给编译器提供正确的选项将产生类似下面这样的错误信息
testlibpq.c:8:22: libpq-fe.h: No such fileor 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
指向这类问题的错误信息会是类似下面这个样子.
testlibpq.o: In function `main':
testlibpq.o(.text+0x60): undefinedreference to `PQsetdbLogin'
testlibpq.o(.text+0x71): undefinedreference to `PQstatus'
testlibpq.o(.text+0xa4): undefinedreference to `PQerrorMessage'
这意味着你忘记 -lpq 了.
/usr/bin/ld: cannot find -lpq
这意味着你忘记这-L 或者没有声明正确的路径.
如果你的代码引用了头文件 libpq-int.h, 而且你不原意修补你的代码以避免使用它,那么从 PostgreSQL7.2 开始, 该文件将在includedir/postgresql/internal/libpq-int.h 里出现, 因此你需要给你的编译器命令行增加合适的 -I 选项.
1.14. 例子程序
Example 1-1. libpq 例子程序 1
[myphp]
/*
* testlibpq.c
*
* Test the C version of libpq,thePostgreSQLfrontend
* library.
* 测试 PostgreSQL 前端库 libpq 的 C 版本
*/
#include
#include
void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
exit(1);
}
main()
{
char *pghost,
*pgport,
*pgoptions,
*pgtty;
char *dbName;
int nFields;
int i,
j;
/* FILE *debug; */
PGconn *conn;
PGresult *res;
/*
* begin, by setting the parameters for abackend connection if the
* parameters are null, then the system willtry to use reasonable
* defaults by looking up environment variablesor, failing that,
* using hardwired constants
*/
pghost = NULL; /* host name of the backendserver */
pgport = NULL; /* port of the backendserver */
pgoptions = NULL; /* special options tostart up the backend
* server */
pgtty = NULL; /* debugging tty for thebackend server */
dbName = "template1";
/* make a connection to the database */
conn = PQsetdb(pghost, pgport, pgoptions,pgtty, dbName);
/*
* check to see that the backend connectionwas successfully made
*/
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr, "Connection todatabase '%s' failed. ", dbName);
fprintf(stderr, "%s",PQerrorMessage(conn));
exit_nicely(conn);
}
/* debug =fopen("/tmp/trace.out","w"); */
/* PQtrace(conn, debug); */
/* start a transaction block */
res = PQexec(conn, "BEGIN");
if (!res || PQresultStatus(res) !=PGRES_COMMAND_OK)
{
fprintf(stderr, "BEGIN command failed");
PQclear(res);
exit_nicely(conn);
}
/*
* should PQclear PGresult whenever it is nolonger needed to avoid
* memory leaks
*/
PQclear(res);
/*
* fetch rows from the pg_database, thesystem catalog of
* databases
*/
res = PQexec(conn, "DECLARE mycursorCURSOR FOR SELECT * FROM pg_database");
if (!res || PQresultStatus(res) !=PGRES_COMMAND_OK)
{
fprintf(stderr, "DECLARE CURSORcommand failed ");
PQclear(res);
exit_nicely(conn);
}
PQclear(res);
res = PQexec(conn, "FETCH ALL inmycursor");
if (!res || PQresultStatus(res) !=PGRES_TUPLES_OK)
{
fprintf(stderr, "FETCH ALL commanddidn't return tuples properly ");
PQclear(res);
exit_nicely(conn);
}
/* first, print out the attribute names */
nFields = PQnfields(res);
for (i = 0; i < nFields; i++)
printf("%-15s", PQfname(res, i));
printf(" ");
/* next, print out the rows */
for (i = 0; i < PQntuples(res); i++)
{
for (j = 0; j < nFields; j++)
printf("%-15s", PQgetvalue(res,i, j));
printf(" ");
}
PQclear(res);
/* close the cursor */
res = PQexec(conn, "CLOSEmycursor");
PQclear(res);
/* commit the transaction */
res = PQexec(conn, "COMMIT");
PQclear(res);
/* close the connection to the database andcleanup */
PQfinish(conn);
/* fclose(debug); */
return 0;
}
[/myphp]
Example 1-2. libpq 例子程序 2
[myphp]
/*
* testlibpq2.c
* 测试异步通知接口
*
* 运行此程序,然后从另外一个窗口的 psql 里运行 NOTIFY TBL2;
*
* 或者,如果你想好玩一点,尝试下面动作∶
* 用下面的语句填充一个数据库∶
*
* 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); NOTIFYTBL2);
*
* 然后∶
*
* INSERT INTO TBL1 values (10);
*
*/
#include
#include "libpq-fe.h"
void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
exit(1);
}
main()
{
char *pghost,
*pgport,
*pgoptions,
*pgtty;
char *dbName;
int nFields;
int i,
j;
PGconn *conn;
PGresult *res;
PGnotify *notify;
/*
* begin, by setting the parameters for abackend connection if the
* parameters are null, then the system willtry to use reasonable
* defaults by looking up environmentvariables or, failing that,
* using hardwired constants
*/
pghost = NULL; /* host name of the backendserver */
pgport = NULL; /* port of the backendserver */
pgoptions = NULL; /* special options tostart up the backend
* server */
pgtty = NULL; /* debugging tty for thebackend server */
dbName = getenv("USER"); /*change this to the name of your test
* database */
/* make a connection to the database */
conn = PQsetdb(pghost, pgport, pgoptions,pgtty, dbName);
/*
* check to see that the backend connectionwas successfully made
*/
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr, "Connection todatabase '%s' failed. ", dbName);
fprintf(stderr, "%s",PQerrorMessage(conn));
exit_nicely(conn);
}
res = PQexec(conn, "LISTENTBL2");
if (!res || PQresultStatus(res) !=PGRES_COMMAND_OK)
{
fprintf(stderr, "LISTEN command failed");
PQclear(res);
exit_nicely(conn);
}
/*
* should PQclear PGresult whenever it is nolonger needed to avoid
* memory leaks
*/
PQclear(res);
while (1)
{
/*
* wait a little bit between checks; waitingwith select()
* would be more efficient.
*/
sleep(1);
/* collect any asynchronous backendmessages */
PQconsumeInput(conn);
/* check for asynchronous notify messages*/
while ((notify = PQnotifies(conn)) != NULL)
{
fprintf(stderr,
"ASYNC NOTIFY of '%s' from backend pid'%d' received ",
notify->relname, notify->be_pid);
free(notify);
}
}
/* close the connection to the database andcleanup */
PQfinish(conn);
return 0;
}
Example 1-3. libpq 例子程序 3
/*
* testlibpq3.c 测试 C 版本的 Libpq--PostgreSQL的前端库.
* 测试二进制游标接口
*
*
*
* 用下面语句填充一个数据库∶
*
* CREATE TABLE test1 (i int4, d real, ppolygon);
*
* INSERT INTO test1 values (1, 3.567,polygon '(3.0, 4.0, 1.0, 2.0)');
*
* INSERT INTO test1 values (2, 89.05,polygon '(4.0, 3.0, 2.0, 1.0)');
*
* 预期的输出是∶
*
* tuple 0: got i = (4 bytes) 1, d = (4bytes) 3.567000, p = (4
* bytes) 2 points boundbox =(hi=3.000000/4.000000, lo =
* 1.000000,2.000000) tuple 1: got i = (4bytes) 2, d = (4 bytes)
* 89.050003, p = (4 bytes) 2 pointsboundbox =
* (hi=4.000000/3.000000, lo =2.000000,1.000000)
*
*
*/
#include
#include "libpq-fe.h"
#include "utils/geo_decls.h" /*for the POLYGON type */
void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
exit(1);
}
main()
{
char *pghost,
*pgport,
*pgoptions,
*pgtty;
char *dbName;
int nFields;
int i,
j;
int i_fnum,
d_fnum,
p_fnum;
PGconn *conn;
PGresult *res;
/*
* begin, by setting the parameters for abackend connection if the
* parameters are null, then the system willtry to use reasonable
* defaults by looking up environmentvariables or, failing that,
* using hardwired constants
*/
pghost = NULL; /* host name of the backendserver */
pgport = NULL; /* port of the backendserver */
pgoptions = NULL; /* special options tostart up the backend
* server */
pgtty = NULL; /* debugging tty for thebackend server */
dbName = getenv("USER"); /*change this to the name of your test
* database */
/* make a connection to the database */
conn = PQsetdb(pghost, pgport, pgoptions,pgtty, dbName);
/*
* check to see that the backend connectionwas successfully made
*/
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr, "Connection todatabase '%s' failed. ", dbName);
fprintf(stderr, "%s",PQerrorMessage(conn));
exit_nicely(conn);
}
/* start a transaction block */
res = PQexec(conn, "BEGIN");
if (!res || PQresultStatus(res) !=PGRES_COMMAND_OK)
{
fprintf(stderr, "BEGIN command failed");
PQclear(res);
exit_nicely(conn);
}
/*
* should PQclear PGresult whenever it is nolonger needed to avoid
* memory leaks
*/
PQclear(res);
/*
* fetch rows from the pg_database, thesystem catalog of
* databases
*/
res = PQexec(conn, "DECLARE mycursorBINARY CURSOR FOR SELECT * FROM test1");
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
{
fprintf(stderr, "DECLARE CURSORcommand failed ");
PQclear(res);
exit_nicely(conn);
}
PQclear(res);
res = PQexec(conn, "FETCH ALL inmycursor");
if (!res || PQresultStatus(res) !=PGRES_TUPLES_OK)
{
fprintf(stderr, "FETCH ALL commanddidn't return tuples properly ");
PQclear(res);
exit_nicely(conn);
}
i_fnum = PQfnumber(res, "i");
d_fnum = PQfnumber(res, "d");
p_fnum = PQfnumber(res, "p");
for (i = 0; i < 3; i++)
{
printf("type[%d] = %d, size[%d] = %d",
i, PQftype(res, i),
i, PQfsize(res, i));
}
for (i = 0; i < PQntuples(res); i++)
{
int *ival;
float *dval;
int plen;
POLYGON *pval;
/* we hard-wire this to the 3 fields weknow about */
ival = (int *) PQgetvalue(res, i, i_fnum);
dval = (float *) PQgetvalue(res, i,d_fnum);
plen = PQgetlength(res, i, p_fnum);
/*
* plen doesn't include the length field soneed to
* increment by VARHDSZ
*/
pval = (POLYGON *) malloc(plen + VARHDRSZ);
pval->size = plen;
memmove((char *) &pval->npts,PQgetvalue(res, i, p_fnum), plen);
printf("tuple %d: got ", i);
printf(" i = (%d bytes) %d, ",
PQgetlength(res, i, i_fnum), *ival);
printf(" d = (%d bytes) %f, ",
PQgetlength(res, i, d_fnum), *dval);
printf(" p = (%d bytes) %d pointsboundbox = (hi=%f/%f, lo = %f,%f) ",
PQgetlength(res, i, d_fnum),
pval->npts,
pval->boundbox.xh,
pval->boundbox.yh,
pval->boundbox.xl,
pval->boundbox.yl);
}
PQclear(res);
/* close the cursor */
res = PQexec(conn, "CLOSEmycursor");
PQclear(res);
/* commit the transaction */
res = PQexec(conn, "COMMIT");
PQclear(res);
/* close the connection to the database andcleanup */
PQfinish(conn);
return 0;
}
[/myphp]
PostgreSQL数据库学习手册之大对象(转)[@more@]
Chapter 2. 大对象
Table of Contents
2.1. 介绍
2.2. 实现特点
2.3. 接口
2.3.1. 创建大对象
2.3.2. 输入大对象
2.3.3. 输出大对象
2.3.4. 打开一个现有的大对象
2.3.5. 向大对象中写数据
2.3.6. 从大对象中读取数据
2.3.7. 对大对象中数据的查找
2.3.8. 关闭一个大对象描述符
2.3.9. 删除一个大对象
2.4. 内建的已注册函数
2.5. 通过Libpq 访问大对象
2.1. 介绍
在 PostgreSQL 7.1 以前的版本里, 记录存储在数据页面里并且单个记录里的数据大小不能超过数据页面的大小.因为数据页面大小是8192 字节(缺省,可以增加到 32768), 所以可存储数据值的上限是相当底的. 为了存储更大的原子数值,PostgreSQL 提供了大对象接口. 这个接口给用户提供对定义为大对象的用户数据的面向文件的接口.
最初,PostgreSQL 4.2 (PostgreSQL 的间接前身) 支持三种大对象的标准实现: 作为 POSTGRES服务器外部的文件扩展,作为由 POSTGRES 管理的外部文件, 以及作为存储在 POSTGRES数据库里面的数据. 这样做容易导致用户的迷惑.结果是,我们只支持把大对象作为数据存储在 PostgreSQL 数据库里.即使这样做令数据访问变得有些慢,但却保证了更严格的数据完整性. 由于历史原因,这种存储机制被称为转置大对象. (我们将在本章中交互使用 转置和大对象来表示同一个意思)。自 PostgreSQL 7.1 开始,所由大对象 都保留在一个叫pg_largeobject的系统表里.
PostgreSQL 7.1 引入了一种新的机制 (外号叫"TOAST"),允许数据行 远远大于独立的数据页面.这样就令大对象接口在一定程度上过时了. 大对象接口剩余的一个优点是它允许对数据的随机访问,也就是说,可以读写小块的大对象.我们准备在将来的版本中为 TOAST 也装备上这样的功能.
本节描述 PostgreSQL 大对象接口 的实现和编程及查询语言接口.我们在本章中使用 libpq C 库作为例子, 但大多数 PostgreSQL 内置的编程接口都支持相同的功能.其它接口可以在内部使用大对象接口以便为 大对象数值提供通用的支持.这里没有描述这些.
2.2. 实现特点
转置大对象把大对象分解成"块" ("chunks"), 然后把块存放在数据库记录里面.在随机读写时使用一个B-tree 索引保证对正确的块(chunk)号的检索
2.3. 接口
PostgreSQL 提供的用于访问大对象的机制, 包括作为用户定义函数的后端的一部分或者作为使用接口的前端应用的一部分, 都在下面描述.对于熟悉 PostgreSQL 4.2的用户, PostgreSQL 有一套新的函数提供更连贯的接口.
注意: 有大对象的操作都必须在一个SQL事务里实现。 这在 PostgreSQL v6.5 里有严格的要求,尽管在以前的版本里就隐含这样的要求,如果忽略这一点会导致错误的表现。
PostgreSQL大对象接口是对 Unix 文件系统的模仿,有仿真的 open(2),read(2), write(2),lseek(2),等. 用户函数调用这些路径从一个大对象中检索她们感兴趣的数据. 例如,如果一个名为mugshot的大对象类型存在,在其中保存面孔的图象,那么可以在mugshot数据上定义一个 叫beard(胡子)的函数.Beard 会检查图片的下三分之一区域, 并且如果存在胡子的话判断胡子的颜色. 整个大对象的值不需要被brard函数缓存起来或者甚至是做些检查. 大对象可以从动态装载的 C 函数或者是链接该库的数据库客户程序访问. PostgreSQL 提供一套过程支持对大对象的打开, 读,写,关闭和搜索。
2.3.1. 创建大对象
过程
Oid lo_creat(PGconn *conn, int mode)
创建一个新的大对象. mode是一个位掩码, 描述新对象的不同属性.这里列出的符号常量在 $PGROOT/src/backend/libpq/libpq-fs.h 列出.访问类型(读,写或者两者)是对位 INV_READ 和 INV_WRITE进行或操作构成的. 掩码的低十六位是大对象要存放于内的存储管理器号.对于除 Berkeley (伯克利)以外的节点,这些位都应总是零. 下面的命令创建一个 (转置的)大对象:
inv_oid = lo_creat(INV_READ|INV_WRITE);
2.3.2. 输入大对象
要把一个操作系统文件输入成为大对象,调用
Oid lo_import(PGconn *conn, const char*filename)
filename 参数指明要被输入成为大对象的操作系统文件路径名.
2.3.3. 输出大对象
要把一个大对象输出为操作系统文件,调用
int lo_export(PGconn *conn, Oid lobjId,const char *filename)
lobjId参数指明要输出的大对象 OID,filename 参数指明操作系统文件的路径名.
2.3.4. 打开一个现有的大对象
要打开一个现存的大对象,调用
int lo_open(PGconn *conn, Oid lobjId, intmode)
参数lobjId指明要打开的大对象的 OID (对象标识). mode位控制该对象是用于读 (INV_READ), 写(INV_WRITE)还是读写. 一个大对象在其创建之前不能被打开. lo_open 返回一个大对象标识用于以后的 lo_read,lo_write, lo_lseek,lo_tell,和 lo_close。
2.3.5. 向大对象中写数据
过程
int lo_write(PGconn *conn, int fd, constchar *buf, size_t len)
从buf中向大对象fd中写len字节. 参数fd必须是前面一个lo_open 调用的返回。 返回实际写的字节数.出错时返回负数.
2.3.6. 从大对象中读取数据
过程
int lo_read(PGconn *conn, int fd, char*buf, size_t len)
从大对象中读取len字节数据到buf中。 fd参数必须是前面的一个 lo_open调用的返回。 返回实际读取的字节数。出错时,返回一个负数。
2.3.7. 对大对象中数据的查找
要改变当前大对象的读或写位置,调用
int lo_lseek(PGconn *conn, int fd, intoffset, int whence)
这个过程把当前fd代表的大对象位置指针移动到 offset指明的新的位置. 参数whence的合法的取值是 SEEK_SET,SEEK_CUR 和SEEK_END.
2.3.8. 关闭一个大对象描述符
可以通过调用
int lo_close(PGconn *conn, int fd)
关闭一个大对象,这里fd是lo_open返回的大对象的描述符.成功时, lo_close 返回零.错误时,返回值是负数.
2.3.9. 删除一个大对象
从数据库中删除一个大对象,调用
Oid lo_unlink(PGconn *conn, Oid lobjId)
lobjId参数声明要删除的大对象的 OID.
2.4. 内建的已注册函数
有两个内建的已注册函数,lo_import 和 lo_export 可以很方便的在 SQL 查询里面使用.下面是一些例子
CREATE TABLE image (
name text,
raster oid
);
INSERT INTO image (name, raster)
VALUES ('beautiful image',lo_import('/etc/motd'));
SELECT lo_export(image.raster, '/tmp/motd')FROM image
WHERE name = 'beautiful image';
2.5. 通过Libpq 访问大对象
Example 2-1是一个例子程序, 显示如何使用libpq里面的大对象接口. 程序的一部分是注释掉的,但仍然保留在源码里面供读者参考.这个程序可以在源码发布的 src/test/examples/testlo.c 里找到.使用libpq里大对象接口的前端应用 应该包括头文件libpq/libpq-fs.h 并且联接 libpq库.
Example 2-1. Libpq 的大对象例子程序
[myphp]
/*--------------------------------------------------------------
*
* testlo.c--
* test using large objects with libpq
*
* Copyright (c) 1994, Regents of theUniversity of California
*
*--------------------------------------------------------------
*/
#include
#include "libpq-fe.h"
#include "libpq/libpq-fs.h"
#define BUFSIZE 1024
/*
* importFile * import file"in_filename" into database as large object "lob
jOid"
*
*/
Oid
importFile(PGconn *conn, char *filename)
{
Oid lobjId;
int lobj_fd;
char buf[BUFSIZE];
int nbytes,
tmp;
int fd;
/*
* open the file to be read in
*/
fd = open(filename, O_RDONLY, 0666);
if (fd < 0)
{ /* error */
fprintf(stderr, "can't open unix file%s ", filename);
}
/*
* create the large object
*/
lobjId = lo_creat(conn, INV_READ |INV_WRITE);
if (lobjId == 0)
fprintf(stderr, "can't create largeobject ");
lobj_fd = lo_open(conn, lobjId, INV_WRITE);
/*
* read in from the Unix file and write tothe inversion file
*/
while ((nbytes = read(fd, buf, BUFSIZE))> 0)
{
tmp = lo_write(conn, lobj_fd, buf, nbytes);
if (tmp < nbytes)
fprintf(stderr, "error while readinglarge object ");
}
(void) close(fd);
(void) lo_close(conn, lobj_fd);
return lobjId;
}
void
pickout(PGconn *conn, Oid lobjId, intstart, int len)
{
int lobj_fd;
char *buf;
int nbytes;
int nread;
lobj_fd = lo_open(conn, lobjId, INV_READ);
if (lobj_fd < 0)
{
fprintf(stderr, "can't open largeobject %d ",
lobjId);
}
lo_lseek(conn, lobj_fd, start, SEEK_SET);
buf = malloc(len + 1);
nread = 0;
while (len - nread > 0)
{
nbytes = lo_read(conn, lobj_fd, buf, len -nread);
buf[nbytes] = ' ';
fprintf(stderr, ">>>%s", buf);
nread += nbytes;
}
free(buf);
fprintf(stderr, " ");
lo_close(conn, lobj_fd);
}
void
overwrite(PGconn *conn, Oid lobjId, intstart, int len)
{
int lobj_fd;
char *buf;
int nbytes;
int nwritten;
int i;
lobj_fd = lo_open(conn, lobjId, INV_READ);
if (lobj_fd < 0)
{
fprintf(stderr, "can't open largeobject %d ",
lobjId);
}
lo_lseek(conn, lobj_fd, start, SEEK_SET);
buf = malloc(len + 1);
for (i = 0; i < len; i++)
buf[i] = 'X';
buf[i] = ' ';
nwritten = 0;
while (len - nwritten > 0)
{
nbytes = lo_write(conn, lobj_fd, buf +nwritten, len - nwritten);
nwritten += nbytes;
}
free(buf);
fprintf(stderr, " ");
lo_close(conn, lobj_fd);
}
/*
* exportFile * export large object"lobjOid" to file "out_filename"
*
*/
void
exportFile(PGconn *conn, Oid lobjId, char*filename)
{
int lobj_fd;
char buf[BUFSIZE];
int nbytes,
tmp;
int fd;
/*
* create an inversion "object"
*/
lobj_fd = lo_open(conn, lobjId, INV_READ);
if (lobj_fd < 0)
{
fprintf(stderr, "can't open largeobject %d ",
lobjId);
}
/*
* open the file to be written to
*/
fd = open(filename, O_CREAT | O_WRONLY,0666);
if (fd < 0)
{ /* error */
fprintf(stderr, "can't open unix file%s ",
filename);
}
/*
* read in from the Unix file and write to theinversion file
*/
while ((nbytes = lo_read(conn, lobj_fd,buf, BUFSIZE)) > 0)
{
tmp = write(fd, buf, nbytes);
if (tmp < nbytes)
{
fprintf(stderr, "error while writing%s ",
filename);
}
}
(void) lo_close(conn, lobj_fd);
(void) close(fd);
return;
}
void
exit_nicely(PGconn *conn)
{
PQfinish(conn);
exit(1);
}
int
main(int argc, char **argv)
{
char *in_filename,
*out_filename;
char *database;
Oid lobjOid;
PGconn *conn;
PGresult *res;
if (argc != 4)
{
fprintf(stderr, "Usage: %sdatabase_name in_filename out_filename ",
argv[0]);
exit(1);
}
database = argv[1];
in_filename = argv[2];
out_filename = argv[3];
/*
* set up the connection
*/
conn = PQsetdb(NULL, NULL, NULL, NULL,database);
/* check to see that the backend connectionwas successfully made */
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr, "Connection todatabase '%s' failed. ", database);
fprintf(stderr, "%s",PQerrorMessage(conn));
exit_nicely(conn);
}
res = PQexec(conn, "begin");
PQclear(res);
printf("importing file %s ",in_filename);
/* lobjOid = importFile(conn, in_filename);*/
lobjOid = lo_import(conn, in_filename);
/*
printf("as large object %d. ",lobjOid);
printf("picking out bytes 1000-2000 ofthe large object ");
pickout(conn, lobjOid, 1000, 1000);
printf("overwriting bytes 1000-2000 ofthe large object with X's ");
overwrite(conn, lobjOid, 1000, 1000);
*/
printf("exporting large object to file%s ", out_filename);
/* exportFile(conn, lobjOid, out_filename);*/
lo_export(conn, lobjOid, out_filename);
res = PQexec(conn, "end");
PQclear(res);
PQfinish(conn);
exit(0);
}
[/myphp]
PostgreSQL数据库学习手册之服务器端编程---体系结构(转)[@more@]
Chapter 7. 体系结构
7.1. PostgreSQL 体系概念
在我们继续之前,我们应该理解一些 PostgreSQL系统体系的基本概念.理解了 PostgreSQL 各部分之间的相互作用能让 我们更容易理解下一章的内容.在数据库的范畴里, PostgreSQL 使用一个简单的"每用户一进程"的 client/server 模型.一个 PostgreSQL 会话由下面一些UNIX进程(程序)组成:
*
一个监控的守护进程 (postmaster),
*
用户的前端应用(如,psql 程序),和
*
一个或者多个后端数据库服务器 (postgres进程本身).
一个 postmaster 管理某台主机上的一定的数据库集合. 这个数据库集合叫做一个(数据库的)集群. 如果某个前端应用想访问某个节点中的某一数据库, 它就会进行与应用链接到一起的接口库(函数)调用(比如,libpq). 库把用户的请求通过网络发给 postmaster (Figure 7-1(a)), postmaster接着便启动一个新的后端服务进程 (Figure 7-1(b)))
Figure 7-1. 联接是如何建立的
并将前端进程和这个新的服务进程联接起来 (Figure 7-1(c)).从这时起,前端进程和后端服务将不再通过 postmaster 而是直接进行通讯.因而, postmaster 总是在运行,等待着联接请求,而前后端进程则是起起停停.libpq 库能够让一个前端与后端做多个联接. 不过,每个后端进程仍然是一个单线程的进程,任意时刻只能执行一个查询. 因此,任何前端到后端之间的通讯都是单线程的.
这种体系结构表明 postmaster 和后端总是跑在同一台机器上(数据库服务器),而前端应用可以在任何地方运行.必须牢记这一点, 因为在客户机上用户可以访问的文件在数据库服务器上不一定可 以访问(或者只能通过不同的路径名访问).
同时我们还应该注意 postmaster 和 postgres服务进程是以 PostgreSQL "超级用户"的用户标识(user-id)运行的.要注意的是 PostgreSQL 超级用户不一定非要是一个特殊 的用户(如,一个叫postgres的用户),尽管有许多系统是用这个用户名.另外, PostgreSQL 超级用户绝不能是 UNIX 超级用户root! 只要是 Unix 系统环境,那么 PostgreSQL 超级用户是一个普通的非特权用户时是最安全的. 在任何情况下,所有与数据库有关的文件都属于这个 PostgreSQL 超级用户
PostgreSQL数据库学习手册之扩展----介绍(转)[@more@]
概述
在本章的剩余部分,我们将讨论你如何通过增加下面几种对象来扩展PostgreSQL SQL 查询语言:
*
函数
*
数据类型
*
操作符
*
聚集
8.1. 扩展性是如何实现的
PostgreSQL 是可扩展的是因为它的操作是表驱动的. 如果你熟悉标准的关系系统,你知道它们把与数据库,表,字段 等信息存储在一个被称为系统表的地方.(有些系统称这些为数据字典).这些表在用户面前表现为表,和其他表一样,只不过 DBMS 把它自己内部的信息存放在此. PostgreSQL 和标准的关系型数据库的系统表有一个关键区别是PostgreSQL 在它的系统表里面存储了更多的信息 -- 不仅是关于表和列/字段的信息,而且还有关于它们的类型, 函数,访问方式等的信息.这些表可以被用户修改,而且由于 PostgreSQL 的内部操作是以这些表为基础的,这就意味着 PostgreSQL 可以被用户所扩展.相比之下,传统的数据库系统只能通过修改 DBMS 内部的硬代码或装载由DBMS 供应商提供的特殊的模块来扩展.
PostgreSQL 还与大多数其他数据库管理器不同的是 它还可以通过动态装载的方法与用户书写的代码偶合在一起. 也就是说,用户可以把一个目标代码文件 (例如,一个共享库)声明为一个新类型或函数的实现, 这时 PostgreSQL 将根据需要装载它们. 用 SQL 写的代码甚至更容易加入到服务器中去.这种可以 "动态地"更改其操作的能力使 PostgreSQL 特别适合于新应用和新存储结构的快速定型.
8.2. PostgreSQL 类型系统
PostgreSQL 的类型系统可以有好几种方法分解开来. 类型可以分为基本类型和复合类型. 基本类型是那些用象 C 这样的语言实现的,比如 int4。 这些数据类型通常与那些常被认为是"抽象数据类型"的类型对应; PostgreSQL 对这些数据类型只能通过用户提供的方法来操作,并且对这些数据类型的特性的理解只限于用户所描述的范围. 复合类型是当用户创建表时创建的.
PostgreSQL 对这些类型的存储方法只有一种(在存储表的所有记录的文件里), 但是用户可以从查询语言中"深入观察"这些属性, 而且可以通过在字段上定义索引(这类)方法来优化对这些类型的检索. PostgreSQL 的基础类型可以进一步分为内建类型和用户定义类型. 内建类型(象int4)是那些编译进入系统里面去的类型. 用户定义类型是那些由用户用稍后提到的方法创建的类型.
8.3. 关于 PostgreSQL 系统表
在介绍了扩展性的基本概念后,我们现在看看系统表实际上是个什么布局. 你目前可以忽略这章,但是如果 没有这一章的信息,后面的一些章节的内容会变得很难懂,所以你最好把这一章打上标记,以备查询.所有 系统表都具有以 pg_开头的名称. 下面的表格包含可能对最终用户有用的信息.(还有许多其他系统表,但 是很难得有机会直接对它们进行查询.)
Table 8-1. PostgreSQL 系统表
表名称 描述
pg_database 数据库
pg_class 表
pg_attribute 表字段
pg_index 索引
pg_proc 函数/过程
pg_type 数据类型(包括基本类型和复合类型)
pg_operator 操作符
pg_aggregate 聚集函数
pg_am 访问方法
pg_amop 访问方法操作符
pg_amproc 访问方法支持函数
pg_opclass 访问方法操作符表
Figure 8-1. 主要的 PostgreSQL 系统表
开发人员手册给出了关于这些表和它们的字段的更多的详细信息. 不过, Figure8-1 显示了系统表的主要成员和它们的字段. (与其他表无关的字段在这里没有显示出来,除非它们是主键的一部分.) 这个表看起来或多或少有些难懂,除非你真正看了这些表的内容而且看了它们之间是如何相关的.从现在开始, 我们要从这个图里面挖出下面这些东西:
*
在后面的几章里,我们将提供一些在系统表上的连接 查询--这些查询展示了我们在扩展系统时所需 要的信息.仔细研究这张图会让我们对这些连接查询(通常是三或四路连接)更容易理解,因为这样 你就能看到在查询里用到的字段是其他表的外部键字.
*
许多不同的特性(表,属性,函数,类型,访问模式等.) 是按照这个结构紧密集成在一起的.因而 一个简单的 create 命令就有可能更改许多这些表.
*
类型和过程是这个图表的核心.
注意: 我们在这里多多少少把过程 函数混起来用.
几乎每个表都包含其他一个或多个表的字段的引用.例如, PostgreSQL经常使用类型签名(例如,函数或操作符的)来标识其他表的唯一记录.
*
有许多字段和关系有明显的含义, 但是还有许多(尤其是那些与访问模式打交道的字段)没有(明显含义).
PostgreSQL数据库学习手册之扩展的 SQL:函数(转)[@more@]
9.1. 介绍
PostgreSQL 提供四种类型的函数:
*
查询语言函数(用 SQL 写的函数)
*
过程语言函数(用诸如 PL/Tcl 或 PL/pgSQL 这样的语言写的函数)
*
内部函数
*
C 语言函数
每种函数都可以以一个基本类型或一个复合类型或是两者的某种组合作为参数. 另外,每种函数都可以返回 一个基本类型或一个复合类型值.定义 SQL 函数更容易些,所以我们将从这里开始.本章的例子还可以在 funcs.sql 和 funcs.c 里找到.
综观全章,如果你阅读一下 CREATE FUNCTION 的手册页应该会对你理解本章的例子很有帮助.
9.2. 查询语言(SQL)函数
SQL 函数执行一个任意 SQL 查询的列表,返回列表里最后一个查询的结果。 它必须是一条 SELECT.在比较简单的情况下(非集合的情况), 返回最后一条查询结果的第一行.(请记住多行结果的"第一行"是不明确的,除非你用 ORDER BY 对结果排序.) 如果最后一个查询碰巧不返回行,那么返回 NULL.
另外,一个 SQL 函数可以声明为返回一个集合,方法是把该函数的 返回类型声明为 SETOF sometype. 这个时候最后一条查询结果的所有行都会被返回.更多的细节在下面讲.
SQL 函数的函数体应该是一个用分号分隔的一条或多条 SQL 语句的列表. 请注意,因为 CREATE FUNCTION 命令的语法要求函数体要封闭在单引号里面,所以在函数体中使用的单引号 (') 必须逃逸,方法是写两个单引号(') 或者 在需要逃逸的单引号之前放一个反斜扛 (').
SQL 函数的参数在查询里可以用 $n 语法引用: $1指第一个参数,$2指第二个参数,以此类推。 如果参数是 复合类型,那么可以用点表示法, 例如,"$1.emp",访问参数里的字段。
9.2.1. 例子
看看下面这个简单的 SQL 函数的例子, 它将用于对一个银行帐号做扣款(借记消费 debit)动作:
CREATE FUNCTION tp1 (integer, numeric)RETURNS integer AS '
UPDATE bank
SET balance = balance - $2
WHERE acctountno = $1;
SELECT 1;'
LANGUAGE 'sql';
一个用户可以象下面这样用这个函数给帐户 17 扣款 $100.00:
SELECT tp1( 17,100.0);
实际上我们可能喜欢函数有一个比常量 "1" 更有用一些的结果.所以更有可能的定义是
CREATE FUNCTION tp1 (integer, numeric)RETURNS numeric AS '
UPDATE bank
SET balance = balance - $2
WHERE accountno = $1;
SELECT balance FROM bank WHERE accountno =$1;
' LANGUAGE SQL;
它修改余额并返回新的余额.
SQL 里面的任何命令集都可以打成一个包,做成一个函数.这些命令可以包含数据修改(也就是说, INSERT,UPDATE, 和DELETE)以及SELECT 查询. 不过,最后的命令必须是一条返回函数声明的返回类型的 SELECT. 另外,如果你想定义那些执行动作但是不返回有用的数值的 SQL 函数, 你可以把它定义成返回 void。这时候它不能以 SELECT 为最后一条语句。比如:
CREATE FUNCTION clean_EMP () RETURNS voidAS '
DELETE FROM EMP
WHERE EMP.salary <= 0;
' LANGUAGE SQL;
SELECT clean_EMP();
clean_emp
-----------
(1 row)
9.2.2. 基本类型的 SQL 函数
最简单的 SQL 函数可能是不带参数,只是返回一个基本类型如 integer 的函数:
CREATE FUNCTION one()
RETURNS integer
AS 'SELECT 1 as RESULT;'
LANGUAGE 'sql';
SELECT one();
one
-----
1
注意我们给函数定义了目标列(名称为 RESULT), 但是激活函数的查询语句的目标列覆盖了函数的目标列.因此,结果的标记是one 而不是RESULT.
定义以基本类型为参数的 SQL 函数几乎一样简单, 注意我们在函数内如何用$1和$2使用参数:
CREATE FUNCTION add_em(integer, integer)
RETURNS integer
AS 'SELECT $1 + $2;'
LANGUAGE 'sql';
SELECT add_em(1, 2) AS answer;
+-------+
|answer |
+-------+
|3 |
+-------+
9.2.3. 复合类型的SQL函数
当我们声明的函数用复合类型做参数时, 我们不仅要声明我们需要哪个参数(像上面我们使用 $1和$2一样),而且要声明参数的字段.比如, 假设 EMP 是一个包含雇员信息的表,并且因此也是该表每行 的复合类型的名字.这里就是一个函数 double_salary,它计算你薪水翻番之后的数值:
CREATE FUNCTION double_salary(EMP) RETURNSinteger AS '
SELECT $1.salary * 2 AS salary;
'LANGUAGE SQL;
SELECT name, double_salary(EMP) AS dream
FROM EMP
WHERE EMP.cubicle ~= point '(2,1)';
name | dream
------+-------
Sam | 2400
请注意这里使用 $1.salary 的语法 选择参数行数值的一个字段.还要注意SELECT命令是如何 使用一个表的名字表示该表的整个当前行作为复合数值.
我们也可以写一个返回复合类型的函数. 下面是一个返回一行 EMP 函数的例子∶
CREATE FUNCTION new_emp() RETURNS EMP AS '
SELECT text ''None'' AS name,
1000 AS salary,
25 AS age,
point ''(2,2)'' AS cubicle'
LANGUAGE 'sql';
在这个例子中我们给每个字段都赋予了一个常量, 当然我们可以用任何计算或表达式来代替这些常量. 注意定义这样的函数的两个重要的问题∶
*
目标列表的顺序必须和与该复合类型相关的表中字段的顺序完全一样. (象我们上面那样给字段的命名和系统无关.)
*
你必须对表达式进行类型转换以匹配复合类型的定义. 否则你将看到下面的错误信息:
ERROR: function declared to return empreturns varchar instead of text at column 1
返回一行(复合类型)的函数可以用作一个表函数,象下面描述地那样。 我们还可以在SQL 表达式的环境里调用它,但是只有在你从该行中 抽取一个字段或者把整个行传递给另外一个接受同样复合类型的函数中 才可以。比如,
SELECT (new_emp()).name;
name
------
None
我们需要一个额外的圆括弧以防止分析器误解∶
SELECT new_emp().name;
ERROR: parser: parse error at or near"."
另外一个选择是使用函数表示法进行字段抽取.解释这些问题的简单方法是 我们通常交互使用attribute(table)和 table.attribute 的表示法∶
SELECT name(new_emp());
name
------
None
--
-- 下面的与这句话相同∶
-- SELECT EMP.name AS youngster FROM EMPWHERE EMP.age < 30
--
SELECT name(EMP) AS youngster
FROM EMP
WHERE age(EMP) < 30;
youngster
-----------
Sam
另外一个使用函数返回行结果的方法是声明另外一个函数, 该函数接受一个行类型参数,然后把函数结果传递给这个第二个函数∶
CREATE FUNCTION getname(emp) RETURNS textAS
'SELECT $1.name;'
LANGUAGE SQL;
SELECT getname(new_emp());
getname
---------
None
(1 row)
9.2.4. SQL 表函数
一个表函数就是一个可以在查询的 FROM 子句里使用的函数。 所有的 SQL 语言函数都可以用这种方法使用,但是它对于返回复合类型的函数特别 有用。如果该函数定义为返回一个基本类型,那么表函数生成一个单字段表。如果该函数定义为返回一个复合类型,那么该表函数生成一个复合类型里每个字段 组成的行。
这里是一个例子:
CREATE TABLE foo (fooid int, foosubid int,fooname text);
INSERT INTO foo VALUES(1,1,'Joe');
INSERT INTO foo VALUES(1,2,'Ed');
INSERT INTO foo VALUES(2,1,'Mary');
CREATE FUNCTION getfoo(int) RETURNS foo AS'
SELECT * FROM foo WHERE fooid = $1;
' LANGUAGE SQL;
SELECT *, upper(fooname) FROM getfoo(1) ASt1;
fooid | foosubid | fooname | upper
-------+----------+---------+-------
1 | 1 | Joe | JOE
(2 rows)
如这个例子显示的那样,我们可以象对待一个普通表的字段一样对待 函数的结果字段。
请注意我们只从该函数中获取了一行。这是因为我们没有说 SETOF。
9.2.5. 返回集合的 SQL 函数
如果一个 SQL 函数声明为返回SETOF sometype. 这时候,该函数的最后的SELECT查询一直执行到结束,并且它 输出的每行都当做该集合的一个元素返回.
这个特性通常用于把函数当作表函数调用。这个时候函数返回的每一行 都成为查询可见的该表的一行。比如,假设表 foo 有着和 上面一样的内容,而我们说:
CREATE FUNCTION getfoo(int) RETURNS setoffoo AS '
SELECT * FROM foo WHERE fooid = $1;
' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
fooid | foosubid | fooname
-------+----------+---------
1 | 1 | Joe
1 | 2 | Ed
(2 rows)
目前,返回集合的函数也可以在一个 SELECT 查询的目标列表里 调用。对于该 SELECT 自己生成的每一行,都会调用这个返回集合 的函数,并且相对该函数的结果集中的每个元素都会生成一个输出行。不过,请注意,这个功能已经废弃了,在将来的版本中可能会被删除。下面就是一个 在目标列表中使用返回集合的函数的例子:
CREATE FUNCTION listchildren(text) RETURNSSETOF text AS
'SELECT name FROM nodes WHERE parent = $1'
LANGUAGE SQL;
SELECT * FROM nodes;
name | parent
-----------+--------
Top |
Child1 | Top
Child2 | Top
Child3 | Top
SubChild1 | Child1
SubChild2 | Child1
(6 rows)
SELECT listchildren('Top');
listchildren
--------------
Child1
Child2
Child3
(3 rows)
SELECT name, listchildren(name) FROM nodes;
name | listchildren
--------+--------------
Top | Child1
Top | Child2
Top | Child3
Child1 | SubChild1
Child1 | SubChild2
(5 rows)
在最后的SELECT里,请注意没有出现Child2, Child3等的行. 这是因为listchildren 为这些输入返回一个空集合, 因此不生成任何输出行.
9.3. 过程语言函数
过程语言函数不是内建于 PostgreSQL 里的。它们是通过可装载模块提供的。请参考相关的 PL 的文档获取关于语法和 AS 子句 如何被 PL 句柄解释的细节。
在标准的 PostgreSQL 版本里有四种可用的过程语言 (PL/pgSQL,PL/Tcl,PL/Perl和 PL/Python),并且可以定义其他语言。 请参考Chapter 18 获取详细信息。
9.4. 内部函数
内部函数是那些用 C 写的函数,它们已经通过静态链接的方式链接进入 PostgreSQL 后端进程里面。 AS 子句给出函数的 C 语言 的名称,它不必与定义给 SQL 使用的名称相同。 (出于向下兼容考虑,一个空的 AS 子句可以被接受,意味着 C 函数名与 SQL 函数名相同。)通常, 所有在后端里出现的内部函数都在数据库初始化时定义为SQL 函数, 但是用户可以用 CREATE FUNCTION 为内部函数创建额外的别名。
内部函数在 CREATE FUNCTION 命令里是带着 语言名字 internal声明的.
表函数
表函数是生成一个行集合的函数,这些行可以是由基本(标量)数据类型, 也可以是由复合(多字段)数据类型组成。它们的使用类似 FROM 中的一个表,视图, 或者是子查询。表函数返回的字段可以象表,视图,或者子查询字段一样的 形式包含在 SELECT,JOIN, 或者WHERE 子句里。
如果表函数返回一个基本数据类型,那么这个单结果字段以函数的名字命名。 如果函数返回一个复合类型,那么结果字段获得和该复合类型的独立字段相同的名字。
一个表函数可以在 FROM 子句里面取别名,单它也可以不用别名。 如果一个函数不用别名在 FROM 子句里使用,那么函数名用作关系名。
表函数可以在 SELECT 语句里面可以使用表的地方用。 比如
CREATE TABLE foo (fooid int, foosubid int,fooname text);
SELECT * FROM foo WHERE fooid = $1;
' LANGUAGE SQL;
SELECT * FROM getfoo(1) AS t1;
SELECT * FROM foo
WHERE foosubid in (select foosubid fromgetfoo(foo.fooid) z
where z.fooid = foo.fooid);
CREATE VIEW vw_getfoo AS SELECT * FROMgetfoo(1);
SELECT * FROM vw_getfoo;
都是合法的语句。
有时候,定义一些可以根据调用方式不同而返回不同字段集合的表函数是有用的。 为了支持这个特性,表函数可以声明为返回伪类型 record。如果我们 在查询中使用这样的函数,那么预期的行结构必须在查询本身中声明,这样系统 才能知道如何分析和规划这个查询。思考一下这个例子:
SELECT *
FROM dblink('dbname=template1', 'selectproname, prosrc from pg_proc')
AS t1(proname name, prosrc text)
WHERE proname LIKE 'bytea%';
dblink 函数执行一个远端的查询(参阅 contrib/dblink)。 它声明伪返回 record,因为它可能用于任何类型的查询。实际的字段集必须在调用查询里声明,这样分析器才知道象 * 这样的东西应该展开 成什么。
有用户想知道pg中copy命令使用二进制文件时二进制文件的格式,这个格式pg的使用文档里其实已经有了,这儿结合例子说明的更具体一些,现解析如下:
先看一下copy命令的语法,然后做个例子,根据例子解析一下二进制文件的格式。
一
COPY命令用于在外部文件(客户端)和表之间传送数据,语法如下:
COPY tablename [ ( column [,...] ) ]
FROM { 'filename' | STDIN }
[ [ WITH ]
[ BINARY ]
[ OIDS ]
[ DELIMITER [ AS ]'delimiter' ]
[ NULL [ AS ] 'null string']
[ CSV [ HEADER ]
[ QUOTE [ AS ] 'quote' ]
[ ESCAPE [ AS ] 'escape' ]
[ FORCE NOT NULL column [,...] ]
COPY { tablename [ ( column[, ...] ) ] | ( query ) }
TO { 'filename' | STDOUT }
[ [ WITH ]
[ BINARY ]
[ OIDS ]
[ DELIMITER [ AS ]'delimiter' ]
[ NULL [ AS ] 'null string']
[ CSV [ HEADER ]
[ QUOTE [ AS ] 'quote' ]
[ ESCAPE [ AS ] 'escape' ]
[ FORCE QUOTE column [, ...]]
下面是pg7.3版本以前的语法,后续的版本仍然支持
COPY [ BINARY ] table_name [WITH OIDS ]
FROM { 'filename' | STDIN }
[ [USING] DELIMITERS'delimiter' ]
[ WITH NULL AS 'null string']
COPY [ BINARY ] table_name [WITH OIDS ]
TO { 'filename' | STDOUT }
[ [USING] DELIMITERS'delimiter' ]
[ WITH NULL AS 'null string']
二
使用copy命令的例子:
1
命令copy to 涉及的表copybinarytest的定义
beigang=# \d+ copybinarytest
资料表 "enterprisedb.copybinarytest"
栏位 | 型别 | 修饰词 | 存储 | Stats target | 描述
---------+-----------------------+--------+----------+--------------+------
id | numeric(10,0) | | main| |
content | charactervarying(30) | | extended | |
有 OIDs: 否
2
表copybinarytest的记录
beigang=# select * fromcopybinarytest;
id | content
----+-------------
1 | beigang
2 | copy binary
3 | abc
(3 行记录)
3
创建命令copy from 涉及的表copybinarytest2
beigang=# create tablecopybinarytest2 as select * from copybinarytest where 1=8;
SELECT 0
4
命令copy from 涉及的表copybinarytest2的定义
beigang=# \d+copybinarytest2;
资料表 "enterprisedb.copybinarytest2"
栏位 | 型别 | 修饰词 | 存储 | Stats target | 描述
---------+-----------------------+--------+----------+--------------+------
id | numeric(10,0) | | main| |
content | charactervarying(30) | | extended | |
有 OIDs: 否
5
用copy to命令以二进制形式把copybinarytest表的内容拷贝到binary文件中
beigang=# copy binarycopybinarytest to 'binary';
COPY 3
beigang=#
6
Binary文件的内容如下图:
Copy命令二进制文件的格式如下:
二进制格式
二进制格式的文件由一个文件头、数据(零个或多个元组)和文件尾构成。文件头和数据按网络字节顺序表示。
文件头
文件头由15个字节的固定区域和一个变长的扩展区域组成。固定区域包括:
签名
11个字节的序列PGCOPY\nFF\r\n\0。
标志位
32位的整数。位的编号从0(最低位)到31(最高位)。当前只有一个标志位被使用,它是第16位,如果它的值是1,表示含有OID,如果它的值是0,表示不含OID。其它的标志位永远是0。
扩展区域
首先是一个32位的整数,表示扩展区域的长度,不包括这个32位的整数自身。当前,它的值是0。第一个元组紧跟在它的后面。
元组
每个元组以一个16位的整数开始,表示元组中域的个数。然后是元组中的每个域。每个域由一个32位的整数开始,它表示域的长度,后面跟着域的数据,-1表示域的值为空值。如果文件中包含OID,则它的值跟在元组的第一个16位整数的后面,而且就算组的域的个数时,OID不被计算在内,它也由一个32位的整数开始,表示OID的长度,当前,OID的长度固定是4字节,以后可能扩展到8字节。
文件尾
文件尾包含一个16位的整数,它的值是-1。
7
确认copybinarytest2表中没有记录
beigang=# select * fromcopybinarytest2;
id | content
----+---------
(0 行记录)
8
用copy from命令把binary文件中的数据拷贝到表从opybinarytest中
beigang=# copy binarycopybinarytest2 from 'binary';
COPY 3
9
确认表copybinarytest2的内容和copybinarytest相同。
beigang=# select * fromcopybinarytest2;
id | content
----+-------------
1 | beigang
2 | copy binary
3 | abc
(3 行记录)
三
以上面的例子为例解析Copy命令二进制文件格式
上图是copy命令导出的二进制数据文件,图中用红色竖线做分隔符,把二进制文件分成不同的段。蓝色数字表示各段的编号,
其中文件头包括第1段到第3段。
第1段是11个字节的序列PGCOPY\nFF\r\n\0;第2段是标志位,占4个字节;第3段是扩展区域,至少4个字节,根据情况可能更多。
元祖包括第4段到第18段
第4、5、6、7、8段是第一条记录:第4段表示该元祖的列数,占2个字节;第5段表示第一列的长度,占4个字节;第6段是第一列的值;第7段是第二列的长度,占4个字节;第8段是第二列的值。
第9、10、11、12、13段是第二条记录,同上。
第14、15、16、17、18段是第三条记录,同上。
文件尾是第19段,占2个字节。