编译(C)语言函数

用 C 写的函数可以编译成可动态装载的对象,然后用于实现用户定义的 SQL 函数。当用户定义的函数第一次被后端调用时,动态装载器把函数的目标码装载入内存,然后把这个函数与正在运行的Postgres 可执行文件链接起来。CREATE FUNCTION 的 SQL 语法用两种方法之一把 SQL 函数与 C 源函数链接起来。如果 SQL 函数与 C 源函数同名,使用语句的第一种形式。AS 子句里的字符串参数是包含已经编译好的可动态装载的对象的完整的路径名。如果 C 函数的名称与 SQL 函数的名称不同,则使用第二种形式。这种形式 AS 子句接受两个字符串参数,第一个是可动态装载目标文件的完整路径,第二个是动态装载器应搜索的链接符号。这个链接符号只是 C 源代码里的函数名。
注意:在第一次使用之后,一个动态装载的用户函数仍然停留在内存中,因而对该函数的更进一步的调用只是简单的符号表查找。
声明目标文件的参数(AS 子句里的字符串)应该是该函数目标文件的完整路径,并用引号括起来。如在 AS 子句里使用了链接符号,链接符号也应该用单引号括起来,并且就应该是 C 源代码里函数的名称。在 Unix 系统里,命令 nm 会打印出一个可动态装载的对象里的所有链接符号。(Postgres 不会自动编译一个函数;该函数必须在使用 CREATE FUNCTION 命令之前编译。参阅下文获取额外信息。)

基本类型的 C 语言函数

下表列出了被装载入 Postgres 的 C 函数里需要的当作参数的 C 类型。"定义在" 列给出了等效的 C 类型定义的实际的头文件(在 .../src/backend/ 目录里)。如果你包含了utils/builtins.h,这些文件将被自动包括。
表 38-1. 内建的 Postgres 类型等效的 C 类型
 

内建类型  C 类型 定义在 
abstime AbsoluteTime utils/nabstime.h
bool bool include/c.h
box (BOX *) utils/geo-decls.h
bytea (bytea *) include/postgres.h
char char N/A
cid CID include/postgres.h
datetime (DateTime *) include/c.h or include/postgres.h
int2 int2 include/postgres.h
int2vector (int2vector *) include/postgres.h
int4 int4 include/postgres.h
float4 float32 or (float4 *) include/c.h or include/postgres.h
float8 float64 or (float8 *) include/c.h or include/postgres.h
lseg (LSEG *) include/geo-decls.h
name (Name) include/postgres.h
oid oid include/postgres.h
oidvector (oidvector *) include/postgres.h
path (PATH *) utils/geo-decls.h
point (POINT *) utils/geo-decls.h
regproc regproc or REGPROC include/postgres.h
reltime RelativeTime utils/nabstime.h
text (text *) include/postgres.h
tid ItemPointer storage/itemptr.h
timespan (TimeSpan *) include/c.h or include/postgres.h
tinterval TimeInterval utils/nabstime.h
uint2 uint16 include/c.h
uint4 uint32 include/c.h
xid (XID *) include/postgres.h
Postgres 内部把基本类型当作"一片内存"看待.定义在某种类型上的用户定义函数实际上定义了 Postgres对(该数据类型)可能的操作.也就是说,Postgres 只是从磁盘读取和存储该数据类型,而使用你定义的函数来输入,处理和输出数据.基本类型可以有下面三种内部形态(格式)之一: 传递数值的类型的长度只能是1,2 或 4 字节.(即便你的计算机支持其他长度的传值类型也是这样).Postgres 本身的传值类型只能是整数.你要仔细定义你的类型,确保它们在任何体系平台上都是相同尺寸(字节).例如,long 型是一个危险的类型因为在一些机器上它是 4 字节而在另外一些机器上是 8 字节,而 int 型在大多数 Unix 机器上都是4字节的(尽管不是在多数个人微机上).在一个 Unix 机器上的 int4 合理的实现可能是:
/* 4-byte integer, passed by value */
typedef int int4;
另外,任何尺寸的定长类型都可以是传递引用型.例如,下面是一个 Postgres 类型的实现:
/* 16-byte structure, passed by reference */
typedef struct
{
    double  x, y;
} Point;
只能使用指向这些类型的指针来在 Postgres 函数里输入和输出.最后,所有变长类型同样也只能通过传递引用的方法来传递.所有变长类型必须以一个4字节长的长度域开始,并且所有存储在该类型的数据必须放在紧接着长度域的存储空间里.长度域是结构的全长(也就是说,包括长度域本身的长度).我们可以用下面方法定义一个 text 类型:
typedef struct {
    int4 length;
    char data[1];
} text;
显然,上面的数据域不够存储任何可能的字串 -- 在 C 中定义这么个结构是不可能的.当处理变长类型时,我们必须仔细分配正确的存储器数量并初始化长度域.例如,如果我们想在一个 text 结构里存储 40 字节,我们可能会使用象下面的代码片段:
#include "postgres.h"
...
char buffer[40]; /* our source data */
...
text *destination = (text *) palloc(VARHDRSZ + 40);
destination->length = VARHDRSZ + 40;
memmove(destination->data, buffer, 40);
...
既然我们已经讨论了基本类型所有的可能结构,我们便可以用实际的函数举一些例子.假设 funcs.c 象下面一样:
#include <string.h>
#include "postgres.h"

/* By Value */
         
int
add_one(int arg)
{
    return(arg + 1);
}

/* By Reference, Fixed Length */

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;
}

/* By Reference, Variable Length */

text *
copytext(text *t)
{
    /*
     * VARSIZE is the total size of the struct in bytes.
     */
    text *new_t = (text *) palloc(VARSIZE(t));
    memset(new_t, 0, VARSIZE(t));
    VARSIZE(new_t) = VARSIZE(t);
    /*
     * VARDATA is a pointer to the data region of the struct.
     */
    memcpy((void *) VARDATA(new_t), /* destination */
           (void *) VARDATA(t),     /* source */
           VARSIZE(t)-VARHDRSZ);        /* how many bytes */
    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);

    memset((void *) new_text, 0, new_text_size);
    VARSIZE(new_text) = new_text_size;
    strncpy(VARDATA(new_text), VARDATA(arg1), VARSIZE(arg1)-VARHDRSZ);
    strncat(VARDATA(new_text), VARDATA(arg2), VARSIZE(arg2)-VARHDRSZ);
    return (new_text);
}
在 OSF/1 (平台上)我们要敲入:
CREATE FUNCTION add_one(int4) RETURNS int4
     AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c';

CREATE FUNCTION makepoint(point, point) RETURNS point
     AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c';

CREATE FUNCTION concat_text(text, text) RETURNS text
     AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c';
                         
CREATE FUNCTION copytext(text) RETURNS text
     AS 'PGROOT/tutorial/funcs.so' LANGUAGE 'c';
On other systems, we might have to make the filename end in .sl (to indicate that it's a shared library).

复合类型的 C 语言函数

复合类型不象 C 结构那样有固定的布局.复合类型的记录可能包含空(null)域.另外,一个属于继承层次一部分的复合类型可能和同一继承范畴的其他成员有不同的域/字段.因此,Postgres 提供一个过程接口用于从 C 里面访问复合类型.在 Postgres 处理一个记录集时,每条记录都将作为一个类型 TUPLE 的不透明(opaque)的结构被传递给你的函数.假设我们为下面查询写一个函数
         * SELECT name, c_overpaid(EMP, 1500) AS overpaid
           FROM EMP
           WHERE name = 'Bill' or name = 'Sam';
在上面的查询里,我们可以这样定义 c_overpaid :
#include "postgres.h"
#include "executor/executor.h"  /* for GetAttributeByName() */

bool
c_overpaid(TupleTableSlot *t, /* the current instance of EMP */
           int4 limit)
{
    bool isnull = false;
    int4 salary;
    salary = (int4) GetAttributeByName(t, "salary", &isnull);
    if (isnull)
        return (false);
    return(salary > limit);
}
GetAttributeByName 是 Postgres 系统函数,用来返回当前记录的字段值.它有三个参数:类型为 TUPLE 的传入函数的参数,需要的字段名称,以及一个用以确定字段是否为空(null)的返回参数指针.GetAttributeByName 会把数据正确的对齐,这样你就可以把返回值转换成合适的类型.例如,如果你有一个字段的名称就是类型名,调用 GetAttributeByName 就会看起来象:
char *str;
...
str = (char *) GetAttributeByName(t, "name", &isnull)
下面的查询让 Postgres 知道 c_overpaid 函数:
* CREATE FUNCTION c_overpaid(EMP, int4) RETURNS bool
     AS 'PGROOT/tutorial/obj/funcs.so' LANGUAGE 'c';
当然还有其他方法在 C 函数里构造新的记录或修改现有记录,这些方法都太复杂,不适合在本手册里讨论.

书写代码

我们现在转到了书写编程语言函数的更难的阶段.要注意:本手册此章的内容不会让你成为程序员.在你尝试用 C 书写用于 Postgres 的函数之前,你必须对 C 有很深的了解(包括对指针的使用和 malloc 存储器管理).虽然可以用 C 以外的其他语言如 FORTRAN 和 Pascal 书写用于 Postgres 的共享函数,但通常很麻烦(虽然是完全可能的),因为其他语言并不遵循和 C 一样的调用习惯.也就是说,其他语言与 C 的传参和返回值的方式不一样.因此我们假设你的编程语言函数是用 C 写的.

以基本类型为参数的 C 函数可以用直接的风格书写。内建的 Postgres 类型的 C 等效物可以通过把文件 PGROOT/src/backend/utils/builtins.h 做为头文件包含到 C 文件里访问。可以向 C 文件的头部增加

#include <utils/builtins.h>
这一行实现这些。

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