Postgresql扩展Sql-hook

  • 基于PostgreSql 9.5.13

在我们查看Postgresql源码时,经常发现Hook的使用. 例如:

1
2
3
4
5
6
7
8
9
10
11
PlannedStmt *
planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
PlannedStmt *result;

if (planner_hook)
result = (*planner_hook) (parse, cursorOptions, boundParams);
else
result = standard_planner(parse, cursorOptions, boundParams);
return result;
}

Hook是Window消息处理机制的一个平台,应用程序可以在上面设置子程序以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。
当消息到达后,在目标窗口处理函数之前处理它, 钩子机制允许应用程序截获处理Window消息或特定事件。 即钩子先获取控制权,进行加工处理。
Postgresql中的Hook机制,更多是中断与替换操作。改变其标准流程。



原理

Hook本质实际就是Static的函数指针。

工作原理:每个Hook是由全局性的函数指针构成的,其默认初始化值为NULL。 当数据库调用的时候,首先会检测是否为NULL, 不是则优先调用函数,否则执行标准函数。

Hook函数指针的值改变在于服务器启动,通过函数 process_session_preload_libraries(); 加载共享库. 于.so寻找 _PG_init _PG_fini 函数,来进行函数初始化与卸载操作 [与其余插件区别在于:Hook插件有入口函数]。 在其具体操作流程,根据是否修改指定的Hook来参照是否中断或者更改其执行流程。

共享库与初始化

shared_preload_libraries = ''

1
2
3
4
5
6
7
8
9
10
11
guc.c 配置;
{
{"shared_preload_libraries", PGC_POSTMASTER, CLIENT_CONN_PRELOAD, //rocky_name with config; -- reload way
gettext_noop("Lists shared libraries to preload into server."),
NULL,
GUC_LIST_INPUT | GUC_LIST_QUOTE | GUC_SUPERUSER_ONLY
},
&shared_preload_libraries_string, //-程序变量名;
"",
NULL, NULL, NULL
},
1
2
3
4
5
6
7
8
9
//fmgr/dfmgr.c:加载共享lib库,并执行其初始化;
PG_init = (PG_init_t) pg_dlsym(file_scanner->handle, "_PG_init");
if (PG_init)
(*PG_init) ();

//fmgr/dfmgr.c:卸载lib库,移除hook 并 重置;
PG_fini = (PG_fini_t) pg_dlsym(file_scanner->handle, "_PG_fini");
if (PG_fini)
(*PG_fini) ();

设置函数指针:
当数据库载入共享库时[设置share_preload_libiaries],首先会将其载入内存中,然后从共享库调用_PG_init, 通过此函数来加载自己的Hook函数;

contrib插件

1
2
3
4
5
6
void
_PG_init(void)
{
prev_proccessutility_hook = ProcessUtility_hook; //将我们定义的函数指针,指向Pg系统原有函数地址; 本身NULL; -- 起地址交换作用;
ProcessUtility_hook = record_oper_info; //将系统指向我们需要其执行的函数;
}

取消函数指针设置:
那么与之相对应的,你要写一个_PG_fini函数在卸载(drop extension xxxx)的时候使用,也就是移除你的hook并且把它重置为之前的指针值。

1
2
3
4
5
void
_PG_fini(void)
{
ProcessUtility_hook = prev_proccessutility_hook; //卸载lib库时,指向原有函数;
}

Postgresql 常见Hooks:

1
2
3
4
5
6
7
8
9
10
11
12
13
extern PGDLLIMPORT check_password_hook_type check_password_hook;     //处理用户密码时调用的Hook, 可以对用户的密码进行限制,增加密码的规范.
static ExecutorStart_hook_type prev_ExecutorStart = NULL; //处理查询执行开始时调用的Hook;
static ExecutorRun_hook_type prev_ExecutorRun = NULL; //处理查询执行时调用的hook
static ExecutorFinish_hook_type prev_ExecutorFinish = NULL; //处理查询结束时调用的hook
static ExecutorEnd_hook_type prev_ExecutorEnd = NULL; //处理查询完成后调用的hook
static shmem_startup_hook_type prev_shmem_startup_hook = NULL; //在初始化共享内存是调用的hook
static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
static object_access_hook_type next_object_access_hook = NULL;
static ExecutorCheckPerms_hook_type next_exec_check_perms_hook = NULL; //处理访问权限时调用的hook
static ProcessUtility_hook_type next_ProcessUtility_hook = NULL; //通用hook,可以处理很多的过程,。
static ClientAuthentication_hook_type next_client_auth_hook = NULL; //处理链接时调用的hook,可以对链接进行管理;
static needs_fmgr_hook_type next_needs_fmgr_hook = NULL;
static fmgr_hook_type next_fmgr_hook = NULL; //函数调用潜的hook

代码演示

alter_oper.c

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
//alter_oper.c
#include "postgres.h"
#include "fmgr.h"

#include "tcop/utility.h"

#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif


void _PG_init(void);
void _PG_fini(void);

static
ProcessUtility_hook_type prev_proccessutility_hook = NULL;

static
void record_oper_info(Node *parsetree,
const char *queryString, ProcessUtilityContext context,
ParamListInfo params,
DestReceiver *dest, char *completionTag)
{
switch (nodeTag(parsetree))
{
case T_DropStmt:
case T_DropTableSpaceStmt:
case T_DropdbStmt:
ereport(NOTICE, (errmsg("Drop ? it`s not allowd")));
break;

default:
break;
}

standard_ProcessUtility(parsetree, queryString,
context, params,
dest, completionTag);

}


void
_PG_init(void)
{
prev_proccessutility_hook = ProcessUtility_hook; //将我们定义的函数指针,指向Pg系统原有函数地址; 本身NULL; -- 起参数交换作用;
ProcessUtility_hook = record_oper_info; //将系统指向我们需要其执行的函数;
}


void
_PG_fini(void)
{
ProcessUtility_hook = prev_proccessutility_hook; //卸载lib库时,指向原有函数;
}

alter_oper.control

1
2
3
4
5
# alter_oper.control
comment = 'Pg Judicious function'
default_version = '1.0'
module_pathname = '$libdir/alter_oper'
relocatable = true

Makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# contrib/alter_oper/Makefile
MODULES = alter_oper
EXTENSION = alter_oper
PGFILEDESC = "alter_oper - Pg Hook test"
REGRESS = alter_oper

ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
subdir = contrib/alter_oper
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
endif

postgresql.conf

1
2
## postgresql.conf
shared_preload_libraries = 'alter_oper' # (change requires restart)

验证

1
2
3
4
5
6
7
8
9
10
11
shell=# \d
List of relations
Schema | Name | Type | Owner
----------------+------+-------+--------
oracle_catalog | dual | view | Pg
public | std | table | Pg
(2 rows)

shell=# drop table std ;
NOTICE: 00000: Drop ? it`s not allowd ===> 输出;
DROP TABLE

补充

your_extension--1.0.control 里面主要是写一些控制信息,

your_extension--1.0.sql 用于创建一些你需要的数据库对象,比如表,触发器,函数等等。

只有在扩展Sql函数中才会使用,Hook不需要;


模板下载

模板代码下载

欣赏此文? 求鼓励,求支持!