最近因为工作需要,学习了如何在数据库的服务端写编程,使得数据库能够提供自己所需要的功能。简单的说就是在PostgreSQL服务端添加自己的API,然后利用SQL语句调用(我认为这因该是属于二次开发)。下面我就讲一讲,我是如何做的,希望能够给需要的朋友帮助,同时也害怕自己忘记:
我先把自己的代码写下来,把整个过程先说以说。然后根据这个代码我来分析在服务端写代码需要注意的地方:
1.我是先用c语言吧自己想要实现的功能实现。
2.然后将这个代码编译成一个动态库文件放在一个地方。
3.做好了上面2步后就可以用SQL语句对这个api建立以个函数,然后我们就可以用像select这样的sql语句调用它了。
#include
#include
#include "pg_common_utils.h"
#define MAX_NAME_LEN 64
/* To be compatible before version. */
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
PG_FUNCTION_INFO_V1(func_auth);
Datum
func_auth(PG_FUNCTION_ARGS)
{
FuncCallContext *funcctx;
int32 max_calls;
int32 call_cntr;
TupleDesc tupdesc;
AttInMetadata *attinmeta;
int32 ret;
VarChar *username, *username1;
VarChar *passwd, *password1;
char *res, *enc_passwd;
char salt[2];
int32 auth = 0;
char *groupname;
char command[200];
if (SRF_IS_FIRSTCALL()) {
MemoryContext oldcontext;
funcctx = SRF_FIRSTCALL_INIT();
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
funcctx ->max_calls = PG_GETARG_UINT32(0);/* 返回的总行数*/
/* 为结果类型制作一个行描述*/
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) {
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context"
"that cannot accept type record")));
}
/* 生成后面从函数生成行的属性元数据 */
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext);
}
funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr;
max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta;
if (call_cntr < max_calls) {
char **values;
HeapTuple tuple;
Datum result;
/* allocate the memory to values for return row */
values = (char **)palloc(2* sizeof(char*));
values[0] = (char *)palloc(16 * sizeof(char));
values[1] = (char *)palloc(MAX_NAME_LEN);
/* allocate the memory to groupname */
groupname = (char *)palloc(MAX_NAME_LEN);
memset(groupname, 0, MAX_NAME_LEN);
/* connect to SPI database manager */
if ((ret = SPI_connect()) < 0) {
elog(ERROR, "SPI connect failure - SPI_connect returned %d.", ret);
}
/* obtain the arguments. */
username1 = PG_GETARG_VARCHAR_P(1);
username = (VarChar *)palloc(VARSIZE(username1));
memset(username, 0, VARSIZE(username1));
memcpy(VARDATA(username), VARDATA(username1), VARSIZE(username1)-VARHDRSZ);
/* to execute a sql command.*/
snprintf(command, 199, "SELECT table_name1.user_passwd FROM table_name1 WHERE table_name1.user_name='%s'", VARDATA(username));
pfree(username);
ret = SPI_exec(command, 1);
if (ret != SPI_OK_SELECT || SPI_processed < 1) {
auth = 0;
snprintf(values[0], 16, "%d", auth);
values[1][0] = '\0';
pfree(groupname);
groupname = NULL;
SPI_finish();
tuple = BuildTupleFromCStrings(attinmeta, values);
result = HeapTupleGetDatum(tuple);
pfree(values[0]);
pfree(values[1]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
}
/* obtain the password by the global variable SPI_tuptable.*/
res = SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1);
if (res == NULL) {
elog(INFO, "SPI_getvalue error.\n");
auth = 0;
snprintf(values[0], 16, "%d", auth);
strncpy(groupname, "\0", 1);
snprintf(values[1], 2, "%s", groupname);
pfree(groupname);
groupname = NULL;
SPI_finish();
tuple = BuildTupleFromCStrings(attinmeta, values);
result = HeapTupleGetDatum(tuple);
pfree(values[0]);
pfree(values[1]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
}
/* authentication. */
strncpy(salt, res, 2);
password1 = PG_GETARG_VARCHAR_P(2);
passwd = (VarChar *)palloc(VARSIZE(password1));
memset(passwd, 0, VARSIZE(password1));
memcpy(VARDATA(passwd), VARDATA(password1), VARSIZE(password1)-VARHDRSZ);
enc_passwd = crypt32(VARDATA(passwd), salt);
pfree(passwd);
/* password is right. */
if (strcmp(enc_passwd, res) == 0) {
auth = 1;
SPI_finish();
SPI_pfree(res);
if ((ret = SPI_connect()) < 0) {
elog(ERROR, "SPI_connect error, SPI_connect returned:%d\n", ret);
}
/* obtain the groupname. */
ret = SPI_exec("SELECT table_name.grp_name FROM table_name1,table_name WHERE table_name.user_name = table_name1.user_name", 1);
if (ret != SPI_OK_SELECT || SPI_processed < 1) {
elog(INFO, "SPI_exec failure.");
strncpy(groupname, "\0", 1);
snprintf(values[1], 2, "%s", groupname);
pfree(groupname);
groupname = NULL;
SPI_finish();
snprintf(values[0], 16, "%d", auth);
tuple = BuildTupleFromCStrings(attinmeta, values);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);
}
strncpy(groupname, SPI_getvalue(SPI_tuptable->vals[0], SPI_tuptable->tupdesc, 1),
MAX_NAME_LEN);
snprintf(values[1], MAX_NAME_LEN, "%s", groupname);
pfree(groupname);
groupname = NULL;
SPI_finish();
/* password is wrong and authentication failure. */
} else {
auth = 0;
values[1][0]= '\0';
pfree(groupname);
groupname = NULL;
SPI_pfree(res);
SPI_finish();
}
snprintf(values[0], 16, "%d", auth);
tuple = BuildTupleFromCStrings(attinmeta, values);
result = HeapTupleGetDatum(tuple);
pfree(values[0]);
pfree(values[1]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result);
} else {
SRF_RETURN_DONE(funcctx);
}
这是一个我自己实现的API,我自己是可以编译,并且没有问题。由于涉及到公司的很多内部问题,我没有完 全的把代码写出来。所以你们用这上面代码可能不可以编译。
在PostgreSQL数据库服务器端用C语言写代码有2个版本(版本0和版本1),但是目前提倡用版本1写代码。因为版本1提供了许多的宏来消除大多数传递参数和返回结果的复杂性。
在书写和编译C函数的基本规则是:
1.利用pg_config --includedir-server找出PostgreSQL服务器的头文件安装位置。这在编译的时候要用到(编译的命令是:gcc - 文件名。c -I/头文件安装位置)。
2.分配内存时,用PostgreSQL的palloc和pfree函数来代替相应的c库函数的malloc和free。用palloc分配的内存你在每个事务结束后会自动释放,避免了内存泄露。记得在分配了内存后要用memset初始化。
3.大多数的PostgreSQL内部类型定义在postgres.h中,而函数管理接口都在fmgr.h中,所以至少要包括这两个头文件。出于移植性原因,最好先包含postgres.h再包含其他系统或者用户头文件。
4.在目标文件里定义的符号一定不能相互冲突,也不能和定义在PostgreSQL这服务器可可执行代码的符号名字冲突。
同时,在编译的时候为了能够兼容8.2之前的版本,用了一个#ifdef测试,能够包含8.2之前的版本:
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
版本1的规定:
其实,在服务器写代码不一定要用c写,在PostgreSQL数据库中可以用很多的语言,比如: SQL, PL/PGSQL、PERL等语言,在这里除了SQL 和c语言之外的所有语言都统统的称为过程语言。如果你用版本1写代码,就应该熟悉该版本所提供宏的作用。(我用的是c语言而且是版本1,所以只能说说这方面的问题了):
1.函数的定义
版本1风格函数c定义总是这样的:
Datum functionname(PG_FUNCTION_ARGS)
另外,下面 的宏也必须与函数定义出现在同一个源文件中的,这对动态加载时必须的。我用的就是动态加载。哈哈。。。
PG_FUNCTION_INFO_VI(functionname);
2.参数的返回与获取
参数的获取(函数传入的参数)在版本1里是里面的宏实现的,具体的宏是:PG_GETARG_XXX(n)注意n是从0开始的,对于参数的获取,我需要说的是在获取参数之前先到postgre.h文件查清楚该类型的原型。
参数的返回:(就是函数返回值)
一般的参数返回都市通过宏:PG_RETURN_XXX().
由于我返回的不是某一类型,而是以个集合所以这些都对我来说是没有用的。
在版本1中提供集合的返回,并提供了特殊的API。下面我就讲讲这些API的作用:
1. 一个返回集合的函数(SRF)必须遵循版本1的调用方式,同时必须在源代码中包含funcapi.h.
2. 一个返回集合的函数通常为它返回的每个项都调用一次。因此,SRF必须保存足够的转台用于记住它正在做的事情以及在每次调用的时候返回下一项。PostgreSQL提供了FuncCallContext结构体用于帮助控制这个过程。
3.对FuncCallContext结构体的操作:
SRF_IS_FIRSTCALL()用来判断我所写的函数是不是第一次调用,如果是则调用:
SRF_FIRSTCALL_INIT()初始化FuncCallContext
同时利用MemoryContextSwitchTO(funcctx->Multi_memory_ctx)函数将环境切换到适合多次调用的内存环境。
调用函数 SRF_PERCALL_SETUP()为使用FuncCallContext做恰当的设置以及清除任何前面的轮回里面身下的已返回的数据。
如果要返回数据,则调用
SRF_RETURN_NEXT(funcctx, result);
最后,返回结束后使用函数SRF_RETURN_DONE(funcctx);清理并结束SRF.
如果我们要返回是行则要用BuildTupleFromCSTring(AttInMetadata* attinmeta, char **values)制作一HeapTuple。
最后把这个行制作成一个Datum,通过调用:
HeapTupGetDatum( tuple).
代码的编译:
gcc -g -FIPC -C filename.c
编译成一个动态链接库
gcc -shared -filename.so filename.o
利用sql语句创建一个sql语句可以调用的api
CREATE OR REPLACE FUNCTION func_auth(count IN integer, name IN varchar, passwd IN varchar,
result OUT varchar, groupname OUT varchar)
RETURNS SETOF record
AS 'filename, 'func_auth'
LANGUAGE C IMMUTABLE STRICT;
最后我们就可以像:
SELECT *FROM funct_auth(参数,参数,参数);来调用这个函数。
好了,到了这里,一个PostgreSQL数据库服务端的功能扩展就算完成。
如果,你们觉得我那里说的不好,或者不对的地方 欢迎批评指教。
另外我写这篇文章主要参考PostgreSQL的手册,这个手册的链接是: