mirror of https://github.com/postgres/postgres
These hooks can be used in loadable modules. A simple test module is included. The first attempt was done withpull/47/headcd8ce3a
but we lacked handling for NO_INSTALLCHECK in the MSVC scripts (problem solved afterwards by431f1599
) so the buildfarm got angry. This also fixes a couple of issues noticed upon review compared to the first attempt, so the code has slightly changed, resulting in a more simple test module. Author: Fabrízio de Royes Mello, Yugo Nagata Reviewed-by: Andrew Dunstan, Michael Paquier, Aleksandr Parfenov Discussion: https://postgr.es/m/20170720204733.40f2b7eb.nagata@sraoss.co.jp Discussion: https://postgr.es/m/20190823042602.GB5275@paquier.xyz
parent
41a6de41ed
commit
e788bd924c
@ -0,0 +1,4 @@ |
||||
# Generated subdirectories |
||||
/log/ |
||||
/results/ |
||||
/tmp_check/ |
@ -0,0 +1,23 @@ |
||||
# src/test/modules/test_session_hooks/Makefile
|
||||
|
||||
MODULE_big = test_session_hooks
|
||||
OBJS = test_session_hooks.o $(WIN32RES)
|
||||
PGFILEDESC = "test_session_hooks - tests for start and end session hooks"
|
||||
|
||||
REGRESS = test_session_hooks
|
||||
REGRESS_OPTS = --temp-config=$(top_srcdir)/src/test/modules/test_session_hooks/session_hooks.conf
|
||||
# Disabled because these tests require extra configuration with
|
||||
# "shared_preload_libraries=test_session_hooks", which typical
|
||||
# installcheck users do not have (e.g. buildfarm clients).
|
||||
NO_INSTALLCHECK = 1
|
||||
|
||||
ifdef USE_PGXS |
||||
PG_CONFIG = pg_config
|
||||
PGXS := $(shell $(PG_CONFIG) --pgxs)
|
||||
include $(PGXS) |
||||
else |
||||
subdir = src/test/modules/test_session_hooks
|
||||
top_builddir = ../../../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
include $(top_srcdir)/contrib/contrib-global.mk |
||||
endif |
@ -0,0 +1,11 @@ |
||||
test_session_hooks |
||||
================== |
||||
|
||||
test_session_hooks is an example of how to use session start and end |
||||
hooks. |
||||
|
||||
This module will insert into a pre-existing table called "session_hook_log" |
||||
a log activity which happens at session start and end. It is possible |
||||
to control which user information is logged when using the configuration |
||||
parameter "test_session_hooks.username". If set, the hooks will log only |
||||
information of the session user matching the parameter value. |
@ -0,0 +1,37 @@ |
||||
-- |
||||
-- Tests for start and end session hooks |
||||
-- |
||||
-- Only activity from role regress_sess_hook_usr2 is logged. |
||||
CREATE ROLE regress_sess_hook_usr1 SUPERUSER LOGIN; |
||||
CREATE ROLE regress_sess_hook_usr2 SUPERUSER LOGIN; |
||||
\set prevdb :DBNAME |
||||
\set prevusr :USER |
||||
CREATE TABLE session_hook_log(id SERIAL, dbname TEXT, username TEXT, hook_at TEXT); |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
id | dbname | username | hook_at |
||||
----+--------+----------+--------- |
||||
(0 rows) |
||||
|
||||
\c :prevdb regress_sess_hook_usr1 |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
id | dbname | username | hook_at |
||||
----+--------+----------+--------- |
||||
(0 rows) |
||||
|
||||
\c :prevdb regress_sess_hook_usr2 |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
id | dbname | username | hook_at |
||||
----+--------------------+------------------------+--------- |
||||
1 | contrib_regression | regress_sess_hook_usr2 | START |
||||
(1 row) |
||||
|
||||
\c :prevdb :prevusr |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
id | dbname | username | hook_at |
||||
----+--------------------+------------------------+--------- |
||||
1 | contrib_regression | regress_sess_hook_usr2 | START |
||||
2 | contrib_regression | regress_sess_hook_usr2 | END |
||||
(2 rows) |
||||
|
||||
DROP ROLE regress_sess_hook_usr1; |
||||
DROP ROLE regress_sess_hook_usr2; |
@ -0,0 +1,2 @@ |
||||
shared_preload_libraries = 'test_session_hooks' |
||||
test_session_hooks.username = regress_sess_hook_usr2 |
@ -0,0 +1,19 @@ |
||||
-- |
||||
-- Tests for start and end session hooks |
||||
-- |
||||
|
||||
-- Only activity from role regress_sess_hook_usr2 is logged. |
||||
CREATE ROLE regress_sess_hook_usr1 SUPERUSER LOGIN; |
||||
CREATE ROLE regress_sess_hook_usr2 SUPERUSER LOGIN; |
||||
\set prevdb :DBNAME |
||||
\set prevusr :USER |
||||
CREATE TABLE session_hook_log(id SERIAL, dbname TEXT, username TEXT, hook_at TEXT); |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
\c :prevdb regress_sess_hook_usr1 |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
\c :prevdb regress_sess_hook_usr2 |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
\c :prevdb :prevusr |
||||
SELECT * FROM session_hook_log ORDER BY id; |
||||
DROP ROLE regress_sess_hook_usr1; |
||||
DROP ROLE regress_sess_hook_usr2; |
@ -0,0 +1,146 @@ |
||||
/* -------------------------------------------------------------------------
|
||||
* |
||||
* test_session_hooks.c |
||||
* Code for testing start and end session hooks. |
||||
* |
||||
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
||||
* Portions Copyright (c) 1994, Regents of the University of California |
||||
* |
||||
* IDENTIFICATION |
||||
* src/test/modules/test_session_hooks/test_session_hooks.c |
||||
* |
||||
* ------------------------------------------------------------------------- |
||||
*/ |
||||
#include "postgres.h" |
||||
|
||||
#include "access/xact.h" |
||||
#include "commands/dbcommands.h" |
||||
#include "executor/spi.h" |
||||
#include "lib/stringinfo.h" |
||||
#include "miscadmin.h" |
||||
#include "tcop/tcopprot.h" |
||||
#include "utils/snapmgr.h" |
||||
#include "utils/builtins.h" |
||||
|
||||
PG_MODULE_MAGIC; |
||||
|
||||
/* Entry point of library loading/unloading */ |
||||
void _PG_init(void); |
||||
void _PG_fini(void); |
||||
|
||||
/* GUC variables */ |
||||
static char *session_hook_username = "postgres"; |
||||
|
||||
/* Previous hooks on stack */ |
||||
static session_start_hook_type prev_session_start_hook = NULL; |
||||
static session_end_hook_type prev_session_end_hook = NULL; |
||||
|
||||
static void |
||||
register_session_hook(const char *hook_at) |
||||
{ |
||||
const char *username; |
||||
|
||||
StartTransactionCommand(); |
||||
SPI_connect(); |
||||
PushActiveSnapshot(GetTransactionSnapshot()); |
||||
|
||||
/* Check the current user validity */ |
||||
username = GetUserNameFromId(GetUserId(), false); |
||||
|
||||
/* Register log just for configured username */ |
||||
if (strcmp(username, session_hook_username) == 0) |
||||
{ |
||||
const char *dbname; |
||||
int ret; |
||||
StringInfoData buf; |
||||
|
||||
dbname = get_database_name(MyDatabaseId); |
||||
|
||||
initStringInfo(&buf); |
||||
|
||||
appendStringInfo(&buf, "INSERT INTO session_hook_log (dbname, username, hook_at) "); |
||||
appendStringInfo(&buf, "VALUES (%s, %s, %s);", |
||||
quote_literal_cstr(dbname), |
||||
quote_literal_cstr(username), |
||||
quote_literal_cstr(hook_at)); |
||||
|
||||
ret = SPI_exec(buf.data, 0); |
||||
if (ret != SPI_OK_INSERT) |
||||
elog(ERROR, "SPI_execute failed: error code %d", ret); |
||||
} |
||||
|
||||
SPI_finish(); |
||||
PopActiveSnapshot(); |
||||
CommitTransactionCommand(); |
||||
} |
||||
|
||||
/* sample session start hook function */ |
||||
static void |
||||
sample_session_start_hook(void) |
||||
{ |
||||
if (prev_session_start_hook) |
||||
prev_session_start_hook(); |
||||
|
||||
/* consider only normal backends */ |
||||
if (MyBackendId == InvalidBackendId) |
||||
return; |
||||
|
||||
/* consider backends connected to a database */ |
||||
if (!OidIsValid(MyDatabaseId)) |
||||
return; |
||||
|
||||
register_session_hook("START"); |
||||
} |
||||
|
||||
/* sample session end hook function */ |
||||
static void |
||||
sample_session_end_hook(void) |
||||
{ |
||||
if (prev_session_end_hook) |
||||
prev_session_end_hook(); |
||||
|
||||
/* consider only normal backends */ |
||||
if (MyBackendId == InvalidBackendId) |
||||
return; |
||||
|
||||
/* consider backends connected to a database */ |
||||
if (!OidIsValid(MyDatabaseId)) |
||||
return; |
||||
|
||||
register_session_hook("END"); |
||||
} |
||||
|
||||
/*
|
||||
* Module load callback |
||||
*/ |
||||
void |
||||
_PG_init(void) |
||||
{ |
||||
/* Save previous hooks */ |
||||
prev_session_start_hook = session_start_hook; |
||||
prev_session_end_hook = session_end_hook; |
||||
|
||||
/* Set new hooks */ |
||||
session_start_hook = sample_session_start_hook; |
||||
session_end_hook = sample_session_end_hook; |
||||
|
||||
/* Load GUCs */ |
||||
DefineCustomStringVariable("test_session_hooks.username", |
||||
"Username to register log on session start or end", |
||||
NULL, |
||||
&session_hook_username, |
||||
"postgres", |
||||
PGC_SIGHUP, |
||||
0, NULL, NULL, NULL); |
||||
} |
||||
|
||||
/*
|
||||
* Module unload callback |
||||
*/ |
||||
void |
||||
_PG_fini(void) |
||||
{ |
||||
/* Uninstall hooks */ |
||||
session_start_hook = prev_session_start_hook; |
||||
session_end_hook = prev_session_end_hook; |
||||
} |
Loading…
Reference in new issue