文章目录
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; 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); 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 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扩展函数和触发器的建立。本文参考了,还有PostgreSQL 9.3官方文档,,以及8.1中文版文档的对应章节。