pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
用 C 编写的函数可以编译成动态可装载对象,用于实现用户自定义的 SQL 函数。用户自定义函数第一次在后端内被调用时,动态装载器把该函数的目标代码装载到内存中,并把该函数链接到正在运行的 Postgres 可执行文件。CREATE FUNCTION 的 SQL 语法以两种方式之一把 SQL 函数链接到 C 源函数。如果 SQL 函数与 C 源函数同名,则使用该语句的第一种形式。AS 子句中的字符串参数是包含动态可装载编译对象的文件的完整路径名。如果 C 函数的名称与期望的 SQL 函数名不同,则使用第二种形式。此时 AS 子句接受两个字符串参数,第一个是动态可装载对象文件的完整路径名,第二个是动态装载器应搜索的链接符号。这个链接符号就是 C 源代码中的函数名。
动态装载的用户函数在第一次使用后会保留在内存中,以后对该函数的调用只会产生符号表查找这一很小的开销。
指定对象文件的字符串(AS 子句中的字符串)应是该函数目标代码文件的完整路径,用引号括起。如果 AS 子句中使用了链接符号,该链接符号也应用单引号括起,并且应与 C 源代码中函数的名称完全相同。在 Unix 系统上,命令 nm 会打印动态可装载对象中的所有链接符号。(Postgres 不会自动编译函数;它必须在被 CREATE FUNCTION 命令使用之前编译好。更多信息见下文。)
下表给出了将要装载到 Postgres 中的 C 函数的参数所需的 C 类型。“定义于”列给出(.../src/backend/ 目录中)实际定义等价 C 类型的头文件。但如果你包含 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 或 include/postgres.h |
| int2 | int2 | include/postgres.h |
| int2vector | (int2vector *) | include/postgres.h |
| int4 | int4 | include/postgres.h |
| float4 | float32 或 (float4 *) | include/c.h 或 include/postgres.h |
| float8 | float64 或 (float8 *) | include/c.h 或 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 或 REGPROC | include/postgres.h |
| reltime | RelativeTime | utils/nabstime.h |
| text | (text *) | include/postgres.h |
| tid | ItemPointer | storage/itemptr.h |
| timespan | (TimeSpan *) | include/c.h 或 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;
显然,这里所示的 data 字段不足以容纳所有可能的字符串;在 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';
在其他系统上,我们可能必须让文件名以 .sl 结尾(表示它是共享库)。
复合类型没有像 C 结构那样的固定布局。复合类型的实例可能包含空字段。此外,属于某个继承层次的复合类型可能具有与同一继承层次其他成员不同的字段。因此,Postgres 提供了 从 C 访问复合类型字段的过程式接口。当 Postgres 处理一组实例时,每个实例都会作为一个 TUPLE 类型的不透明结构传入你的函数。 假设我们想编写一个函数来回答查询
* 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 类型参数、所需属性的名称,以及一个描述该属性是否为空的返回参数。GetAttributeByName 会正确地对齐数据,因此你可以把它的返回值转换为期望的类型。例如,如果你有一个 name 类型的属性 name,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 函数内构造新实例或修改现有实例,但这些 方法过于复杂,本手册不予讨论。
现在我们转向编写编程语言函数这一更困难的任务。请注意:本手册的这一部分不会使你成为程序员。在尝试为 Postgres 编写 C 函数之前,你必须对 C(包括指针和 malloc 内存管理器的使用)有良好的理解。虽然也许可以把用 C 以外语言编写的函数装载到 Postgres,但这通常很困难(即使在可能的时候),因为其他语言(如 FORTRAN 和 Pascal)往往不遵循与 C 相同的调用约定。也就是说,其他语言在函数之间传递参数和返回值的方式不同。因此,我们将假定你的编程语言函数是用 C 编写的。
带基本类型参数的 C 函数可以以直截了当的方式编写。如果把 作为头文件包含,内建 Postgres 类型的 C 等价物就可以在 C 文件中使用。这可以通过PGROOT/src/backend/utils/builtins.h
#include <utils/builtins.h>
放在 C 源文件的顶部。
构建 C 函数的基本规则如下:
Postgres 的大多数头(include)文件应当已安装在 (见图 2)。你应当总是在 cc 命令行中包含PGROOT/include
-I$PGROOT/include
。有时你可能 发现需要服务器源码本身中的头文件(即需要一个我们疏忽没有安装到 include 中的文件)。那些情况下,你可能需要添加一个或多个
-I$PGROOT/src/backend
-I$PGROOT/src/backend/include
-I$PGROOT/src/backend/port/<PORTNAME>
-I$PGROOT/src/backend/obj
(其中 <PORTNAME> 是移植的名称,例如 alpha 或 sparc)。
分配内存时,使用 Postgres 的例程 palloc 和 pfree,而不是相应的 C 库例程 malloc 和 free。palloc 分配的内存会在每个事务结束时自动释放,从而防止内存泄漏。
总是使用 memset 或 bzero 把结构体的字节清零。有几个例程(如哈希访问方法、哈希连接和排序算法)会计算结构体中原始位的函数。即使你初始化了结构体的所有字段,结构体中仍可能存在若干包含垃圾值的对齐填充字节(结构体中的空洞)。
Postgres 的大多数内部类型都在 postgres.h 中声明,因此始终也包含该文件是个好主意。包含 postgres.h 时还会顺带为你包含 elog.h 和 palloc.h。
编译和装载你的目标代码使其能被动态装载到 Postgres 总是需要特殊的标志。关于如何针对你的特定操作系统进行操作,参见 链接动态装载的函数 的详细说明。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。