热门关键字:  ubuntu  分区  Fedora  linux系统进程  函数

PostgreSQL 8.3文档-V 服务器编程-扩展SQL-C 语言函数

来源: 作者: 时间:2008-05-21 Tag: 点击:

比如,我们可以用下面方法定义一个 text 类型:

typedef struct {
    integer length;
    char data[1];
} text;

显然,上面声明的数据域的长度不足以存储任何可能的字串。因为在 C中不可能声明变长度的结构,所以我们倚赖这样的知识:C 编译器不会对数组下标进行范围检查。我们只需要分配足够的空间,然后把数组当做已经声明为合适长度的变量访问。(这是一个常用的技巧,你可以在许多 C 的教科书中读到。)

当处理变长类型时,我们必须仔细分配正确的存储器数量并正确设置长度域。 例如,如果我们想在一个 text 结构里存储 40 字节, 我们可能会使用象下面的代码片段:

#include "postgres.h"
...
char buffer[40]; /* 我们的源数据 */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
destination->length = VARHDRSZ + 40;
memcpy(destination->data, buffer, 40);
...

VARHDRSZ 和 sizeof(int4) 一样, 但是我们认为用宏 VARHDRSZ 表示附加尺寸是用于变长类型的更好的风格。

表 34-1 列出了书写一个使用了 PostgreSQL 内置类型的 C 函数里需要的知道的哪个 SQL 类型对应哪个 C 类型。"定义在" 列给出了需要包含以获取该类型定义的头文件(实际的定义可能是在包含在列出的文件所包含的文件中。我们建议用户只使用这里定义的接口。)注意,你应该总是首先包括 postgres.h,因为它声明了许多你需要的东西。

表 34-1. 与内建的类型等效的 C 类型
内建类型 C 类型 定义在
abstime AbsoluteTime utils/nabstime.h
boolean bool postgres.h (可能是编译器内置)
box BOX* utils/geo_decls.h
bytea bytea* postgres.h
"char" char (编译器内置)
character BpChar* postgres.h
cid CommandId postgres.h
date DateADT utils/date.h
smallint (int2) int2 或 int16 postgres.h
int2vector int2vector* postgres.h
integer (int4) int4 或 int32 postgres.h
real (float4) float4* postgres.h
double precision (float8) float8* postgres.h
interval Interval* utils/timestamp.h
lseg LSEG* utils/geo_decls.h
name Name postgres.h
oid Oid postgres.h
oidvector oidvector* postgres.h
path PATH* utils/geo_decls.h
point POINT* utils/geo_decls.h
regproc regproc postgres.h
reltime RelativeTime utils/nabstime.h
text text* postgres.h
tid ItemPointer storage/itemptr.h
time TimeADT utils/date.h
time with time zone TimeTzADT utils/date.h
timestamp Timestamp* utils/timestamp.h
tinterval TimeInterval utils/nabstime.h
varchar VarChar* postgres.h
xid TransactionId postgres.h

既然我们已经讨论了基本类型所有的可能结构,我们便可以用实际的函数举一些例子。

 语言函数的版本-0 调用风格

我们先提供"老风格"的调用风格 — 尽管这种做法现在已经不提倡了,但它还是比较容易迈出第一步。在版本-0方法里,C 函数的参数和结果只是用普通 C 风格声明,但是要小心使用上面显示的SQL数据类型的 C 表现形式。

下面是一些例子:

#include "postgres.h"
#include <string.h>

/* 传递数值 */

int
add_one(int arg)
{
    return arg + 1;
}

/* 传递引用,定长 */

float8 *
add_one_float8(float8 *arg)
{
    float8    *result = (float8 *) palloc(sizeof(float8));

    *result = *arg + 1.0;

    return result;
}

Point *
makepoint(Point *pointx, Point *pointy)
{
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;

    return new_point;
}

/* 传递引用,变长 */

text *
copytext(text *t)
{
    /*
     * VARSIZE 是结构以字节计的总长度
     */
    text *new_t = (text *) palloc(VARSIZE(t));
    VARATT_SIZEP(new_t) = VARSIZE(t);
    /*
     * VARDATA 是结构中一个指向数据区的指针
     */
    memcpy((void *) VARDATA(new_t), /* 目的 */
           (void *) VARDATA(t),     /* 源 */
           VARSIZE(t)-VARHDRSZ);    /* 多少字节 */
    return new_t;
}

text *
concat_text(text *arg1, text *arg2)
{
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    VARATT_SIZEP(new_text) = new_text_size;
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1)-VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ);
    return new_text;
}

假设上面的代码放在文件 funcs.c 并且编译成了共享目标,我们可以用下面的命令为 PostgreSQL 定义这些函数:

CREATE FUNCTION add_one(integer) RETURNS integer
     AS 'DIRECTORY/funcs', 'add_one'
     LANGUAGE C STRICT;

-- 注意:重载了名字为 add_one() 的 SQL 函数
CREATE FUNCTION add_one(double precision) RETURNS double precision
     AS 'DIRECTORY/funcs', 'add_one_float8'
     LANGUAGE C STRICT;

CREATE FUNCTION makepoint(point, point) RETURNS point
     AS 'DIRECTORY/funcs', 'makepoint'
     LANGUAGE C STRICT;

CREATE FUNCTION copytext(text) RETURNS text
     AS 'DIRECTORY/funcs', 'copytext'
     LANGUAGE C STRICT;

CREATE FUNCTION concat_text(text, text) RETURNS text
     AS 'DIRECTORY/funcs', 'concat_text'
     LANGUAGE C STRICT;

这里的 DIRECTORY 代表共享库文件的目录 (比如PostgreSQL教程目录,那里包含本节使用的例子的代码)。(更好的风格应该是在加了 DIRECTORY 到搜索路径之后, 在 AS 子句里只使用 'funcs',不管怎样,我们都可以省略和系统相关的共享库扩展,通常是 .so 或 .sl。)

请注意我们把函数声明为"strict"(严格),意思是说如果任何输入值为 NULL,那么系统应该自动假设一个 NULL 的结果。这样处理可以让我们避免在函数代码里面检查 NULL 输入。如果不这样处理,我们就得明确检查空值,比如为每个传递引用的参数检查空指针。(对于传值类型的参数,我们甚至没有办法检查!)

尽管这种老风格的调用风格用起来简单,它确不太容易移植;在一些系统上,我们用这种方法传递比 int 小的数据类型就会碰到困难。而且,我们没有很好的返回 NULL 结果的办法,也没有除了把函数严格化以外的处理 NULL 参数的方法。下面要讲的版本-1的方法则解决了这些问题。

 C 语言函数的版本-1调用风格

版本-1 调用风格依赖宏来消除大多数传递参数和结果的复杂性。版本-1 风格函数的 C 定义总是下面这样

Datum funcname(PG_FUNCTION_ARGS)

另外,下面的宏

PG_FUNCTION_INFO_V1(funcname);

也必须出现在同一个源文件里(通常就可以写在函数自身前面)。 对那些内部-语言函数而言,不需要调用这个宏, 因为PostgreSQL目前假设内部函数都是版本-1。 不过,对于动态链接的函数,它是必须的。

在版本-1函数里, 每个实际参数都是用一个对应该参数的数据类型的 PG_GETARG_xxx()宏抓取的,结果是用返回类型的 PG_RETURN_xxx()宏返回的。 PG_GETARG_xxx() 接受要抓取的函数参数的编号作为其参数,编号是从 0 开始的。 PG_RETURN_xxx() 接受实际要返回的数值为自身的参数。

下面是和上面一样的函数,但是是用版本-1风格编的:

#include "postgres.h"
#include <string.h>
#include "fmgr.h"

/* 传递数值 */

PG_FUNCTION_INFO_V1(add_one);
         
Datum
add_one(PG_FUNCTION_ARGS)
{
    int32   arg = PG_GETARG_INT32(0);

    PG_RETURN_INT32(arg + 1);
}

/* 传递引用,定长 */

PG_FUNCTION_INFO_V1(add_one_float8);

Datum
add_one_float8 (PG_FUNCTION_ARGS)
{
    /* 用于 FLOAT8 的宏,隐藏其传递引用的本质 */
    float8   arg = PG_GETARG_FLOAT8(0);

    PG_RETURN_FLOAT8(arg + 1.0);
}

PG_FUNCTION_INFO_V1(makepoint);

Datum
makepoint(PG_FUNCTION_ARGS)
{
    /* 这里,我们没有隐藏 Point 的传递引用的本质 */
    Point     *pointx = PG_GETARG_POINT_P(0);
    Point     *pointy = PG_GETARG_POINT_P(1);
    Point     *new_point = (Point *) palloc(sizeof(Point));

    new_point->x = pointx->x;
    new_point->y = pointy->y;
       
    PG_RETURN_POINT_P(new_point);
}

/* 传递引用,变长 */

PG_FUNCTION_INFO_V1(copytext);

Datum
copytext(PG_FUNCTION_ARGS)
{
    text     *t = PG_GETARG_TEXT_P(0);
    /*
     * VARSIZE 是结构以字节计的总长度
     */
    text     *new_t = (text *) palloc(VARSIZE(t));
    SET_VARSIZE(new_t, VARSIZE(t));
    /*
     * VARDATA 是结构中指向数据区的一个指针
     */
    memcpy((void *) VARDATA(new_t), /* 目的 */
           (void *) VARDATA(t),     /* 源 */
           VARSIZE(t)-VARHDRSZ);    /* 多少字节 */
    PG_RETURN_TEXT_P(new_t);
}

PG_FUNCTION_INFO_V1(concat_text);

Datum
concat_text(PG_FUNCTION_ARGS)
{
    text  *arg1 = PG_GETARG_TEXT_P(0);
    text  *arg2 = PG_GETARG_TEXT_P(1);
    int32 new_text_size = VARSIZE(arg1) + VARSIZE(arg2) - VARHDRSZ;
    text *new_text = (text *) palloc(new_text_size);

    SET_VARSIZE(new_text, new_text_size);
    memcpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ);
    memcpy(VARDATA(new_text) + (VARSIZE(arg1)-VARHDRSZ),
           VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ);
    PG_RETURN_TEXT_P(new_text);
}

用到的 CREATE FUNCTION 命令和用于老风格的等效的命令一样。

乍一看,版本-1的编码好象只是无目的地蒙人。但是它们的确给我们许多改进,因为宏可以隐藏许多不必要的细节。 一个例子在add_one_float8的编码里,这里我们不再需要不停叮嘱自己 float8 是传递引用类型。 另外一个例子是用于变长类型的宏 GETARG 隐藏了抓取"toasted"(烤炉)(压缩的或者线外存储的)值需要做的处理。

版本-1的函数另一个巨大的改进是对 NULL 输入和结果的处理。 宏 PG_ARGISNULL(n) 允许一个函数测试每个输入是否为 NULL (当然,这件事只是对那些没有声明为 "strict" 的函数有必要)。 因为如果有PG_GETARG_xxx() 宏,输入参数是从零开始计算的。请注意,除非我们检验过参数不是 NULL,否则我们不应该执行 PG_GETARG_xxx()。 要返回一个 NULL 结果,执行一个 PG_RETURN_NULL(),这样对严格的和不严格的函数都有效。

在新风格的接口中提供的其它的选项是 PG_GETARG_xxx() 宏的两个变种。第一个, PG_GETARG_xxx_COPY() 保证返回一个指定参数的副本,该副本是可以安全地写入的。 (普通的宏有时候会返回一个指向物理存储在表中的某值的指针,因此我们不能写入该指针。 用 PG_GETARG_xxx_COPY() 宏保证获取一个可写的结果。) 第二个变体由 PG_GETARG_xxx_SLICE() 宏组成,它接受三个参数。第一个是参数的个数(与上同)。 第二个和第三个是要返回的偏移量和数据段的长度。 偏移是从零开始计算的,一个负数的长度则要求返回该值的剩余长度的数据。 这些过程提供了访问大数据值的中部分的更有效的方法,特别是数据的存储类型是"external"的时候。 (一个字段的存储类型可以用 ALTER TABLE tablename ALTER COLUMN colname SET STORAGE storagetype指定。storagetype 是 plain,external, extended 或 main 之一。)

最后,版本-1的函数调用风格也让我们可能返回一个结果集 (Section 34.9.10)并且实现触发器函数(Chapter 35)和过程语言调用句柄(Chapter 48)。 版本-1 的代码也比版本-0的更容易移植,因为它没有违反 C 标准对函数调用协议的限制。更多的细节请参阅源程序中的src/backend/utils/fmgr/README。

 书写代码

在我们转到更深的话题之前,我们要先讨论一些PostgreSQLC 语言函数的编码规则。虽然可以用 C 以外的其他语言如书写用于PostgreSQL的共享函数,但通常很麻烦(虽然是完全可能的),因为其他像 C++,FORTRAN,或者 Pascal 这样的语言并不遵循和 C 的调用习惯。也就是说,其他语言与C的传递参数和返回值的方式不一样。因此我们假设你的编程语言函数是用 C 写的。

书写和制作 C 函数的基本规则如下:

  • 使用 pg_config --includedir-server 找出PostgreSQL服务器的头文件在你的系统(或者是你的用户所运行的系统)中的安装位置。
  • 编译和连接你的代码,使之可以动态装在到 PostgreSQL 中,这总是需要一些特殊的标志。参阅 34.9.6 节获取如何在你的操作系统里完成这个任务的细节。
  • 记得为你的共享库定义一个“magic block”(标识块),如我们在34.9.1节说的那样。
  • 当分配存储器时,用 PostgreSQL 的函数pallocpfree取代相应的 C 库函数mallocfree。用palloc分配的存储器在每个事务结束时会自动释放,避免了内存泄露。
  • memset对你的结构里的字节清零。如果不这么做,就很难支持 hash 索引和 hash 连接,因为我们必须从自己的数据结构中选出最具特征的位来计算 hash。即使你初始化了你的结构的所有域,仍然有可能有几个对齐字节(结构中的洞)含有垃圾值。
  • 大多数的 PostgreSQL 内部类型定义在postgres.h,而函数管理器接口(PG_FUNCTION_ARGS等等。)都在fmgr.h,所以你至少应该包括这两个文件。出于移植性原因,最好先包括postgres.h,然后再包括其它系统或者用户头文件。包含了postgres.h将自动包含c.helog.hpalloc.h
  • 在目标文件里定义的符号一定不能相互冲突,也不能和定义在 PostgreSQL 服务器可执行代码中的符号名字冲突。如果你看到了与此相关的错误信息,那么你必须给你的函数或者变量重命名。

 编译和链接动态链接的函数

在你能够使用由 C 写 PostgreSQL 扩展函数之前, 你必须用一种特殊的方法编译和链接它们,这样才能生成可以被服务器动态地装载的文件。 准确地说,我们需要创建一个共享库。

如果需要超出本节所包含范围的信息,那么你应该阅读你的操作系统的文档, 特别是 C 编译器cc和链接器ld的手册页。另外,PostgreSQL 源代码里包含几个可以运行的例子,它们在contrib目录里。不过,如果你依赖这些例子,那么你自己的模块就会需要 PostgreSQL 源代码的存在才能编译了。

创建共享库和链接可执行文件类似:首先把源代码编译成目标文件,然后把目标文件链接起来。目标文件需要创建成位置无关码(position-independent code/PIC),概念上就是在可执行程序装载它们的时候,它们可以放在内存里的任何地方,(用于可执行文件的目标文件通常不是用这个方式编译的。)链接动态库的命令包含特殊标志,与链接可执行文件的命令是有区别的。(至少理论上如此 — 在一些系统里会更恶心。)

在下面的例子里,我们假设你的源程序代码在foo.c文件里并且将创建成名字叫foo.so的共享库。,除非我们另外注明,否则中介的对象文件将叫做foo.o。一个共享库可以包含多个对象文件,不过我们在这里只用一个。

 BSD/OS

创建 PIC 的编译器标志是 -fpic。创建共享库的链接器标志是 -shared

   gcc -fpic -c foo.c
   ld -shared -o foo.so foo.o

上面方法适用于版本 4.0 的 BSD/OS。

 FreeBSD

创建 PIC 的编译器标志是 -fpic'。创建共享库的链接器标志是 -shared

   gcc -fpic -c foo.c
   gcc -shared -o foo.so foo.o

上面方法适用于版本 3.0 的 FreeBSD.

 HP-UX

创建 PIC 的系统编译器标志是 +z。如果使用 GCC 则是 -fpic。 创建共享库的链接器标志是 -b。因此

   cc +z -c foo.c

   gcc -fpic -c foo.c

然后

   ld -b -o foo.sl foo.o

HP-UX 使用 .sl 做共享库扩展,和其它大部分系统不同。

IRIX

PIC 是缺省,不需要使用特殊的编译器选项。 生成共享库的链接器选项是 -shared

   cc -c foo.c
   ld -shared -o foo.so foo.o
 Linux

创建 PIC 的编译器标志是 -fpic。在一些平台上的一些环境下, 如果-fpic不能用那么必须使用-fPIC。 参考 GCC 的手册获取更多信息。创建共享库的编译器标志是-shared。一个完整的例子看起来象:

   cc -fpic -c foo.c
   cc -shared -o foo.so foo.o
 MacOS X

这里是一个例子。这里假设开发工具已经安装好了。

   cc -c foo.c
   cc -bundle -flat_namespace -undefined suppress -o foo.so foo.o
 NetBSD

创建 PIC 的编译器标志是 -fpic。对于 ELF 系统, 带-shared标志的编译命令用于链接共享库。 在老的非 ELF 系统里,使用ld -Bshareable

   gcc -fpic -c foo.c
   gcc -shared -o foo.so foo.o
 OpenBSD

创建 PIC 的编译器标志是-fpicld -Bshareable 用于链接共享库。

   gcc -fpic -c foo.c
   ld -Bshareable -o foo.so foo.o
 Solaris

创建 PIC 的编译器命令是用 Sun 编译器时为-KPIC而用 GCC 时为-fpic。链接共享库时两个编译器都可以用-G或者用 GCC 时还可以是-shared

   cc -KPIC -c foo.c
   cc -G -o foo.so foo.o

   gcc -fpic -c foo.c
   gcc -G -o foo.so foo.o
 Tru64 UNIX

PIC 是缺省,因此编译命令就是平常的那个。 带特殊选项的ld用于链接:

   cc -c foo.c
   ld -shared -expect_unresolved '*' -o foo.so foo.o

用 GCC 代替系统编译器时的过程是一样的;不需要特殊的选项。

UnixWare

SCO 编译器创建 PIC 的标志是-K PIC,GCC 是 -fpic。 链接共享库时 SCO 编译器用 -G 而 GCC 用-shared

   cc -K PIC -c foo.c
   cc -G -o foo.so foo.o

   gcc -fpic -c foo.c
   gcc -shared -o foo.so foo.o


技巧: 如果你觉得这些步骤实在太复杂,那么你应该考虑使用 GNU Libtool,它把平台的差异隐藏在了一个统一的接口里。

生成的共享库文件然后就可以装载到 PostgreSQL里面去了。在给 CREATE FUNCTION 命令声明文件名的时候,我们必须声明共享库文件的名字而不是中间目标文件的名字。请注意你可以在 CREATE FUNCTION 命令上忽略系统标准的共享库扩展 (通常是.so或.sl),并且出于最佳的兼容性考虑也应该忽略。

回去看看 Section 34.9.1 获取有关服务器预期在哪里找到共享库的信息。

 扩展的制作架构

如果你在考虑发布你的PostgreSQL扩展模块,那么给他们设置一个可移植的制作系统可能会相当困难。因此PostgreSQL安装提供了一个用于扩展的制作架构,叫做 PGXS,这样,简单的扩展模块可以在一个已经安装了的服务器上制作了。请注意这个架构并不是企图用于实现一个统一的、可以用于制作所有与PostgreSQL相关的软件的架构;它只是用于自动化那些简单的服务器扩展模块的制作。对于更复杂的包,你还是需要书写自己的制作系统。

要在你的扩展中使用该架构,你必须写一个简单的 makefile。在该makefile里,你需要设置一些变量并且最后包括全局的 PGXS makefile。 下面是一个制作一个包含一个共享库,一个 SQL 脚本,和一个文档文本文件的叫做isbn_issn的例子:

MODULES = isbn_issn
DATA_built = isbn_issn.sql
DOCS = README.isbn_issn

PGXS := $(shell pg_config --pgxs)
include $(PGXS)

最后两行应该总是一样的。在文件的前面,你赋予变量或者增加客户化的 make 规则。



相关文章:
pgSQL 集群过程
PL/SQL学习笔记:游标
Pgbouncer 管理员手册
Pgbouncer 用户手册
Pgbouncer 介绍
使用dbi-link在PG里访问异构数据库
PostgreSQL查看数据库表的大小
PostgreSQL设置默认的search path(schema)
PostgreSQL如何让数据按照中文排序
PostgreSQL利用用户定制的聚集函数选取每个分组的
PostgreSQL的FTI与中文全文索引的实践
PostgreSQL 8.3文档-V 服务器编程-扩展SQL-C 语言
在Windows系统上安装和运行PostgreSQL的常见问题
PostgreSQL常见问题
PostgreSQL 简介
Ubuntu系统下安装和配置PostgreSQL 8.1
影响postgresql性能的几个重要参数
PostgreSQL相关
如何从网络上登录其它计算机的PostgreSQL
在Ubuntu和Debian系统下安装PostgreSQL
导入文本文件中存放的数据
从其他机器登陆PostgreSQL
PostgreSQL入门
PostgreSQL 7.2 教程
PostgreSQL 8.0.0入门之创建数据库
postgresql-数据库物理存储
PL/pgSQL的结构
Postgresql-基本语句
PL/pgSQL控制结构
在 Windows 上安装客户端