文章目录

PostgreSQL是一个对扩展和程序开发很友好的数据库,有很多的外部扩展接口,很易于扩展。本文使用PostgreSQL的C扩展编写扩展函数,并提供给触发器调用,本文的例子来源与官方文档。

PostgreSQL的扩展函数可以有“version-0”和“version-1”两种格式,但是触发器只支持“version-1”格式,这也是在“version-0”基础上做过改进的一种格式。一段“version-1”的触发器代码如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include "postgres.h"
#include "executor/spi.h" /* 你用SPI的时候要用的头文件 */
#include "commands/trigger.h" /* 用触发器时要用的头文件 */
#include "utils/rel.h" /* ... and relations */
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
extern Datum trigf(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(trigf);
Datum
trigf(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
TupleDesc tupdesc;
HeapTuple rettuple;
char *when;
bool checknull = false;
bool isnull;
int ret, i;
/* 确信自己是作为触发器触发的 */
if (!CALLED_AS_TRIGGER(fcinfo))
elog(ERROR, "trigf: not fired by trigger manager");
/* 返回给执行者的行 */
if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
rettuple = trigdata->tg_newtuple;
else
rettuple = trigdata->tg_trigtuple;
/* 检查空值 */
if (!TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)
&& TRIGGER_FIRED_BEFORE(trigdata->tg_event))
checknull = true;
if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
when = "before";
else
when = "after ";
tupdesc = trigdata->tg_relation->rd_att;
/* 与 SPI 管理器连接 */
if ((ret = SPI_connect()) < 0)
elog(INFO, "trigf (fired %s): SPI_connect returned %d", when, ret);
/* 获取关系中的行数量 */
ret = SPI_exec("SELECT count(*) FROM ttest", 0);
if (ret < 0)
elog(NOTICE, "trigf (fired %s): SPI_exec returned %d", when, ret);
/* count(*) 返回 int8,所以要小心转换 */
i = (int) DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[0],
SPI_tuptable->tupdesc,
1,
&isnull));
elog (NOTICE, "trigf (fired %s): there are %d tuples in ttest", when, i);
SPI_finish();
if (checknull)
{
(void) SPI_getbinval(rettuple, tupdesc, 1, &isnull);
if (isnull)
rettuple = NULL;
}
return PointerGetDatum(rettuple);
}

保存为trigger_func.c,Makefile可以通过pg_config来产生,运行pg_config --help可以看到帮助文档,

可以发现,--pgxs参数是用于写扩展的Makefile,运行就可以找到所需的Makefile,

1
2
[postgres@anzhy my_extension]$ pg_config --pgxs
/home/postgres/postgres2/lib/pgxs/src/makefiles/pgxs.mk

打开这个Makefile文件,发现开头有一段文档描述,如下,

根据文档的提示,可以写如下Makefile用于编译trigger_func.c

1
2
3
4
5
MODULES = trigger_func
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

然后就可以用make编译了,如下,

注意,要使用和PostgreSQL对应的官方文档中的代码才可以编译,否则可能会报错。然后将编译出来的.so文件copy到PostgreSQL的lib目录下,之后,启动PostgreSQL,并连接test数据库,在test数据库中建表ttest,

1
2
3
CREATE TABLE ttest (
x integer
);

然后注册触发器函数,

1
2
3
CREATE FUNCTION trigf() RETURNS trigger
AS '$libdir/trigger_func', 'trigf'
LANGUAGE C;

由于扩展程序是放在lib目录下,所以,要以$libdir做开头,其他情况可以参考官方文档。注意,如果只是创建一个普通函数,那么这里的返回就不用是trigger,可以是int,相应的,之前的C代码部分,返回也要修改,可以参考1。之后,就可以创建触发器,

1
2
3
4
5
CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();
CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
FOR EACH ROW EXECUTE PROCEDURE trigf();

触发器建立完之后,就可以做一些简单的测试了,如下,

可见,新建的触发器已经发挥了作用。如果是单纯的函数,则更加简单一些,可以在上面的c文件之后,加入如下代码,

1
2
3
4
5
6
7
8
9
10
PG_FUNCTION_INFO_V1(add_ab);
Datum
add_ab(PG_FUNCTION_ARGS)
{
int32 arg_a=PG_GETARG_INT32(0);
int32 arg_b=PG_GETARG_INT32(1);
PG_RETURN_INT32(arg_a+arg_b);
}

重新编译,并将.so文件copy到PostgreSQL的lib目录下。然后重启PostgreSQL,并新增函数,如下

1
2
3
4
CREATE FUNCTION add(int,int)
RETURNS int
AS '$libdir/trigger_func', 'add_ab'
LANGUAGE C;

测试一下,如下,

可见,函数已经生效。

以上是PostgreSQL扩展函数和触发器的建立。本文参考了2,还有PostgreSQL 9.3官方文档34,以及8.1中文版文档5的对应章节。

文章目录

欢迎来到Valleylord的博客!

本博的文章尽量原创。