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, int mode)
参数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, const char *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, int offset, 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 the University of California
*
*--------------------------------------------------------------
*/
#include <stdio.h>
#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\n", filename);
}
/*
* create the large object
*/
lobjId = lo_creat(conn, INV_READ | INV_WRITE);
if (lobjId == 0)
fprintf(stderr, "can't create large object\n");
lobj_fd = lo_open(conn, lobjId, INV_WRITE);
/*
* read in from the Unix file and write to the inversion file
*/
while ((nbytes = read(fd, buf, BUFSIZE)) > 0)
{
tmp = lo_write(conn, lobj_fd, buf, nbytes);
if (tmp < nbytes)
fprintf(stderr, "error while reading large object\n");
}
(void) close(fd);
(void) lo_close(conn, lobj_fd);
return lobjId;
}
void
pickout(PGconn *conn, Oid lobjId, int start, 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 large object %d\n",
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, "\n");
lo_close(conn, lobj_fd);
}
void
overwrite(PGconn *conn, Oid lobjId, int start, 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 large object %d\n",
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, "\n");
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 large object %d\n",
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\n",
filename);
}
/*
* read in from the Unix file and write to the inversion 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\n",
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: %s database_name in_filename out_filename\n",
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 connection was successfully made */
if (PQstatus(conn) == CONNECTION_BAD)
{
fprintf(stderr, "Connection to database '%s' failed.\n", database);
fprintf(stderr, "%s", PQerrorMessage(conn));
exit_nicely(conn);
}
res = PQexec(conn, "begin");
PQclear(res);
printf("importing file %s\n", in_filename);
/* lobjOid = importFile(conn, in_filename); */
lobjOid = lo_import(conn, in_filename);
/*
printf("as large object %d.\n", lobjOid);
printf("picking out bytes 1000-2000 of the large object\n");
pickout(conn, lobjOid, 1000, 1000);
printf("overwriting bytes 1000-2000 of the large object with X's\n");
overwrite(conn, lobjOid, 1000, 1000);
*/
printf("exporting large object to file %s\n", out_filename);
/* exportFile(conn, lobjOid, out_filename); */
lo_export(conn, lobjOid, out_filename);
res = PQexec(conn, "end");
PQclear(res);
PQfinish(conn);
exit(0);
}
[/myphp]