在PostgreSQL内核的学习过程中,可以尝试向内核中添加一些函数,扩展PostgreSQL的功能。同时可以增加自己对PG内核的理解。这里我们以简单的添加一个helloworld函数为例,分析一下这个过程中涉及到的相关源码。
PostgreSQL添加pg_helloworld函数
这里总结一下如何向PostgreSQL中添加内核函数,以helloworld
为例,添加一个内核函数pg_helloworld
,显示Hello PostgreSQL!
。在添加之前,我们输入select pg_helloworld()
,因为PostgreSQL中没有该内核函数,所以显示如下错误:
postgres=# select pg_helloworld();
ERROR: function pg_helloworld() does not exist
LINE 1: select pg_helloworld();
^
HINT: No function matches the given name and argument types. You might need to add explicit type casts.
这里我们实现这个函数。过程如下:
- 在
src/include/catalog/pg_proc.dat
中添加如下声明
# add function helloworld() by chirpyli
{
oid => '9999', descr => 'Hello PostgreSQL',
proname => 'pg_helloworld', prorettype => 'text',
proargtypes => '', prosrc => 'pg_helloworld' },
其中含义如下:
oid:对象id,唯一不重复
descr:函数描述信息
proname:函数名称
prorettype:返回值类型
proargtypes:参数列表
prosrc:函数名称
- 在
src/backend/utils/adt/pseudotypes.c
中添加函数pg_helloworld
/*
* pg_helloworld
* function to show 'Hello PostgreSQL!'
*/
Datum
pg_helloworld(PG_FUNCTION_ARGS)
{
char str[] = "Hello PostgreSQL!";
PG_RETURN_TEXT_P(cstring_to_text(str));
}
这里说明一下参数,能够直接用SQL语句调用的函数(prosrc),他的参数必须是PG_FUNCTION_ARGS
,其定义(src/include/fmgr.h
)如下:
/* Standard parameter list for fmgr-compatible functions */
#define PG_FUNCTION_ARGS FunctionCallInfo fcinfo
typedef struct FunctionCallInfoBaseData *FunctionCallInfo;
typedef struct FunctionCallInfoBaseData
{
FmgrInfo *flinfo; /* ptr to lookup info used for this call */
fmNodePtr context; /* pass info about context of call */
fmNodePtr resultinfo; /* pass or return extra info about result */
Oid fncollation; /* collation for function to use */
#define FIELDNO_FUNCTIONCALLINFODATA_ISNULL 4
bool isnull; /* function must set true if result is NULL */
short nargs; /* # arguments actually passed */
#define FIELDNO_FUNCTIONCALLINFODATA_ARGS 6
NullableDatum args[FLEXIBLE_ARRAY_MEMBER];
} FunctionCallInfoBaseData;
typedef Datum (*PGFunction) (FunctionCallInfo fcinfo);
typedef struct FmgrInfo
{
PGFunction fn_addr; /* pointer to function or handler to be called */
Oid fn_oid; /* OID of function (NOT of handler, if any) */
short fn_nargs; /* number of input args (0..FUNC_MAX_ARGS) */
bool fn_strict; /* function is "strict" (NULL in => NULL out) */
bool fn_retset; /* function returns a set */
unsigned char fn_stats; /* collect stats if track_functions > this */
void *fn_extra; /* extra space for use by handler */
MemoryContext fn_mcxt; /* memory context to store fn_extra in */
fmNodePtr fn_expr; /* expression parse tree for call, or NULL */
} FmgrInfo;
- 验证是否添加成功,重新编译
make && make install
,初始化数据库initdb
,psql
连接数据库,select pg_helloworld()
查看是否添加成功,结果如下,添加成功。
postgres=# select pg_helloworld();
pg_helloworld
-------------------
Hello PostgreSQL!
(1 row)
源码分析
上面成功的添加了pg_helloworld
函数后,我们深入思考一下,进行源码分析,看一下其中的细节。数据库处理函数大概的流程是用户发起了调用函数的SQL语句,PG要解析SQL语句,生成语法解析树,首先要识别出是调用系统函数,然后在pg_proc
系统表中查询是否有该函数,这个过程是在语义分析阶段做的,最后生成计划树。我们一步一步进行源码分析。具体分析跟踪源码的时候可以用select pg_backend_pid()
进行分析。
解析部分
这部分主要是在词法语法分析阶段,识别出是调用函数。关于SQL调用的前期过程以及词法分析过程可参考上一篇PostgreSQL中表名,列名的长度限制,里面有相关的源码分析。这里不再细述。这里只关心解析出函数部分。
解析部分调用主流程如下:
main(int argc, char *argv[])
--> PostmasterMain(argc, argv);
--> ServerLoop();
--> BackendStartup(port);
--> BackendRun(port);
--> PostgresMain(ac, av, port->database_name, port->user_name);
--> for (;;) // 在这里不断接收客户端的请求,处理
--> exec_simple_query(const char *query_string)
--> pg_parse_query(query_string)