Add support event triggers on authenticated login


注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、Waiting for PostgreSQL 17 – Add support event triggers on authenticated login,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


@



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、深入理解PostgreSQL数据库之Add support event triggers on authenticated login


学习时间:

2024-03-28 12:31:44 星期四


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master+Oracle19C+MySQL8.0

postgres=# select version();
                                                  version                                                   
------------------------------------------------------------------------------------------------------------
 PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)

postgres=#

#-----------------------------------------------------------------------------#

SQL> select * from v$version;          

BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0


#-----------------------------------------------------------------------------#

mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)

mysql>

功能使用背景说明

有兴趣的小伙伴们可以去看一下该版patch的实现,如下:

此提交引入了登录事件触发器,允许在用户连接上触发一些操作。这对于日志记录或连接检查以及环境的某些个性化很有用。使用详细信息在包含的文档中进行了描述,但很快使用与其他触发器相同:创建返回 event_trigger 的函数,然后在登录事件上创建事件触发器

为了防止没有触发器时的连接时间开销,提交引入了 pg_database.dathasloginevt 标志,该标志指示数据库具有活动的登录触发器。该标志由 CREATE/ALTER EVENT TRIGGER 命令设置,并在连接时未找到活动触发器时取消设置


接下来,看一下该功能的相关使用,如下:

很久以前我们就有了 EVENT 触发器。虽然可能并没有真正使用它们(至少是直接使用它们,因为我们使用经常使用它们的 pgl_ddl_deploy),但它们肯定有它们的用例。

# 我这里的参数配置:

...
log_checkpoints = off
log_connections = on
log_disconnections = on
...

现在,我们可以在每次会话创建时执行一些代码。它看起来像:

[postgres@localhost:~/test/bin]$ ./psql 
2024-03-27 21:54:09.128 PDT [12139] LOG:  connection received: host=[local]
2024-03-27 21:54:09.129 PDT [12139] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 21:54:09.129 PDT [12139] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (17devel)
Type "help" for help.

postgres=# CREATE OR REPLACE FUNCTION login_validate() RETURNS event_trigger LANGUAGE plpgsql AS $$
postgres$# DECLARE
postgres$# BEGIN
postgres$#     IF session_user <> 'postgres' THEN
postgres$#         raise exception 'Go AWAY';
postgres$#     END IF;
postgres$#     raise log 'user login: %', session_user;
postgres$# END;
postgres$# $$;
CREATE FUNCTION
postgres=# 
postgres=# CREATE EVENT TRIGGER login_validate
postgres-#   ON login
postgres-#   EXECUTE FUNCTION login_validate();
CREATE EVENT TRIGGER
postgres=# \q
2024-03-27 21:55:11.224 PDT [12139] LOG:  disconnection: session time: 0:01:02.095 user=postgres database=postgres host=[local]
[postgres@localhost:~/test/bin]$

有了这个,如果我尝试登录到我的数据库,并使用 postgres 帐户发出一些查询,我会得到:

[postgres@localhost:~/test/bin]$ ./psql -U postgres -d postgres -X -c 'select now()'
2024-03-27 21:58:32.917 PDT [12428] LOG:  connection received: host=[local]
2024-03-27 21:58:32.917 PDT [12428] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 21:58:32.917 PDT [12428] LOG:  connection authorized: user=postgres database=postgres application_name=psql
2024-03-27 21:58:32.919 PDT [12428] LOG:  user login: postgres
2024-03-27 21:58:32.919 PDT [12428] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
              now              
-------------------------------
 2024-03-27 21:58:32.920223-07
(1 row)

2024-03-27 21:58:32.920 PDT [12428] LOG:  disconnection: session time: 0:00:00.004 user=postgres database=postgres host=[local]
[postgres@localhost:~/test/bin]$

但如果另一个用户尝试做同样的事情:

postgres=# create role test superuser password '1' login;
CREATE ROLE
postgres=# grant all privileges on database postgres to test;
GRANT
postgres=#
[postgres@localhost:~/test/bin]$ ./psql -U test -d postgres -X -c 'select now()'
2024-03-27 22:02:18.784 PDT [12812] LOG:  connection received: host=[local]
2024-03-27 22:02:18.784 PDT [12812] LOG:  connection authenticated: user="test" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 22:02:18.785 PDT [12812] LOG:  connection authorized: user=test database=postgres application_name=psql
2024-03-27 22:02:18.787 PDT [12812] FATAL:  Go AWAY
2024-03-27 22:02:18.787 PDT [12812] CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE
psql: error: connection to server on socket "/tmp/.s.PGSQL.5432" failed: FATAL:  Go AWAY
CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE
2024-03-27 22:02:18.787 PDT [12812] LOG:  disconnection: session time: 0:00:00.003 user=test database=postgres host=[local]
[postgres@localhost:~/test/bin]$

当然,还有更简单的方法来禁止用户连接。但强制执行某些策略可能会有所帮助,例如:每个连接都必须设置application_name

postgres=# drop function login_validate cascade;
NOTICE:  drop cascades to event trigger login_validate
DROP FUNCTION
postgres=# CREATE OR REPLACE FUNCTION login_validate() RETURNS event_trigger LANGUAGE plpgsql AS $$
postgres$# DECLARE
postgres$# BEGIN
postgres$#     IF '' = current_setting('application_name') THEN
postgres$#         raise exception 'You don''t have application_name set!';
postgres$#     END IF;
postgres$#     raise log 'user logged with application name: %', current_setting('application_name');
postgres$# END;
postgres$# $$;
CREATE FUNCTION
postgres=# 
postgres=# CREATE EVENT TRIGGER login_validate
postgres-#   ON login
postgres-#   EXECUTE FUNCTION login_validate();
CREATE EVENT TRIGGER
postgres=#
[postgres@localhost:~/test/bin]$ ./psql 
2024-03-27 22:48:02.963 PDT [13644] LOG:  connection received: host=[local]
2024-03-27 22:48:02.964 PDT [13644] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 22:48:02.964 PDT [13644] LOG:  connection authorized: user=postgres database=postgres application_name=psql
2024-03-27 22:48:02.966 PDT [13644] LOG:  user logged with application name: psql
2024-03-27 22:48:02.966 PDT [13644] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
psql (17devel)
Type "help" for help.

postgres=# \q
2024-03-27 22:48:07.029 PDT [13644] LOG:  disconnection: session time: 0:00:04.066 user=postgres database=postgres host=[local]
[postgres@localhost:~/test/bin]$

但如果我尝试在不设置 application_name 的情况下运行最简单的应用程序:

## 安装相关依赖

[postgres@localhost:~/test/bin]$ sudo yum install perl-DBI -y
...
[postgres@localhost:~/test/bin]$ sudo yum install perl-DBD-Pg -y
...
[postgres@localhost:~/test/bin]$ perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=postgres;host=127.0.0.1;port=5432")->selectall_arrayref("select now()")->[0]->[0]'
2024-03-27 22:54:08.881 PDT [52215] LOG:  connection received: host=127.0.0.1 port=33408
2024-03-27 22:54:08.886 PDT [52215] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:119)
2024-03-27 22:54:08.886 PDT [52215] LOG:  connection authorized: user=postgres database=postgres
2024-03-27 22:54:08.888 PDT [52215] FATAL:  You don't have application_name set!
2024-03-27 22:54:08.888 PDT [52215] CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE
2024-03-27 22:54:08.889 PDT [52215] LOG:  disconnection: session time: 0:00:00.008 user=postgres database=postgres host=127.0.0.1 port=33408
DBI connect('dbname=postgres;host=127.0.0.1;port=5432','',...) failed: FATAL:  You don't have application_name set!
CONTEXT:  PL/pgSQL function login_validate() line 5 at RAISE at -e line 1.
Can't call method "selectall_arrayref" on an undefined value at -e line 1.
[postgres@localhost:~/test/bin]$

如果我通过环境变量提供 application_name 名称,它将立即通过:

[postgres@localhost:~/test/bin]$ PGAPPNAME=test perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=postgres;host=127.0.0.1;port=5432")->selectall_arrayref("select now()")->[0]->[0]'
2024-03-27 22:57:36.872 PDT [52400] LOG:  connection received: host=127.0.0.1 port=36198
2024-03-27 22:57:36.873 PDT [52400] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:119)
2024-03-27 22:57:36.874 PDT [52400] LOG:  connection authorized: user=postgres database=postgres application_name=test
2024-03-27 22:57:36.876 PDT [52400] LOG:  user logged with application name: test
2024-03-27 22:57:36.876 PDT [52400] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
2024-03-27 22:57:36.876919-07
2024-03-27 22:57:36.877 PDT [52400] LOG:  disconnection: session time: 0:00:00.005 user=postgres database=postgres host=127.0.0.1 port=36198
[postgres@localhost:~/test/bin]$

或通过连接选项:

[postgres@localhost:~/test/bin]$ perl -le 'use DBI; print DBI->connect("dbi:Pg:dbname=postgres;host=127.0.0.1;port=5432;application_name=whatever")->selectall_arrayref("select now()")->[0]->
[0]'
2024-03-27 22:59:06.474 PDT [52524] LOG:  connection received: host=127.0.0.1 port=54582
2024-03-27 22:59:06.480 PDT [52524] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:119)
2024-03-27 22:59:06.480 PDT [52524] LOG:  connection authorized: user=postgres database=postgres application_name=whatever
2024-03-27 22:59:06.485 PDT [52524] LOG:  user logged with application name: whatever
2024-03-27 22:59:06.485 PDT [52524] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
2024-03-27 22:59:06.488951-07
2024-03-27 22:59:06.491 PDT [52524] LOG:  disconnection: session time: 0:00:00.017 user=postgres database=postgres host=127.0.0.1 port=54582
[postgres@localhost:~/test/bin]$

使用相同的方法,我们可以制作触发器,禁止某些用户在特定时间登录,或者只是在登录时为用户预填充一些全局内容。


补丁实现原理分析

接着上面的触发器,看一下如下内容:

[postgres@localhost:~/test/bin]$ 
[postgres@localhost:~/test/bin]$ ./psql 
2024-03-27 23:12:13.922 PDT [52868] LOG:  connection received: host=[local]
2024-03-27 23:12:13.924 PDT [52868] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 23:12:13.924 PDT [52868] LOG:  connection authorized: user=postgres database=postgres application_name=psql
2024-03-27 23:12:13.926 PDT [52868] LOG:  user logged with application name: psql
2024-03-27 23:12:13.926 PDT [52868] CONTEXT:  PL/pgSQL function login_validate() line 7 at RAISE
psql (17devel)
Type "help" for help.

postgres=# select * from pg_database;
 oid |  datname  | datdba | encoding | datlocprovider | datistemplate | datallowconn | dathasloginevt | datconnlimit | datfrozenxid | datminmxid | dattablespace | datcollate  |  datctype   | datlocale | daticurules | datcollversion |                         datacl                         
-----+-----------+--------+----------+----------------+---------------+--------------+----------------+--------------+--------------+------------+---------------+-------------+-------------+-----------+-------------+----------------+--------------------------------------------------------
   1 | template1 |     10 |        6 | c              | t             | t            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=c/postgres,postgres=CTc/postgres}
   4 | template0 |     10 |        6 | c              | t             | f            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             |                | {=c/postgres,postgres=CTc/postgres}
   5 | postgres  |     10 |        6 | c              | f             | t            | t              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=Tc/postgres,postgres=CTc/postgres,test=CTc/postgres}
(3 rows)

postgres=# \df
                            List of functions
 Schema |      Name      | Result data type | Argument data types | Type 
--------+----------------+------------------+---------------------+------
 public | login_validate | event_trigger    |                     | func
(1 row)

postgres=# drop function login_validate cascade;
NOTICE:  drop cascades to event trigger login_validate
DROP FUNCTION
postgres=# 
postgres=# select * from pg_database;
 oid |  datname  | datdba | encoding | datlocprovider | datistemplate | datallowconn | dathasloginevt | datconnlimit | datfrozenxid | datminmxid | dattablespace | datcollate  |  datctype   | datlocale | daticurules | datcollversion |                         datacl                         
-----+-----------+--------+----------+----------------+---------------+--------------+----------------+--------------+--------------+------------+---------------+-------------+-------------+-----------+-------------+----------------+--------------------------------------------------------
   1 | template1 |     10 |        6 | c              | t             | t            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=c/postgres,postgres=CTc/postgres}
   4 | template0 |     10 |        6 | c              | t             | f            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             |                | {=c/postgres,postgres=CTc/postgres}
   5 | postgres  |     10 |        6 | c              | f             | t            | t              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=Tc/postgres,postgres=CTc/postgres,test=CTc/postgres}
(3 rows)

postgres=# \q
2024-03-27 23:13:02.143 PDT [52868] LOG:  disconnection: session time: 0:00:48.221 user=postgres database=postgres host=[local]
[postgres@localhost:~/test/bin]$ 
[postgres@localhost:~/test/bin]$ ./psql 
2024-03-27 23:13:05.130 PDT [53078] LOG:  connection received: host=[local]
2024-03-27 23:13:05.131 PDT [53078] LOG:  connection authenticated: user="postgres" method=trust (/home/postgres/test/bin/test/pg_hba.conf:117)
2024-03-27 23:13:05.131 PDT [53078] LOG:  connection authorized: user=postgres database=postgres application_name=psql
psql (17devel)
Type "help" for help.

postgres=# select * from pg_database;
 oid |  datname  | datdba | encoding | datlocprovider | datistemplate | datallowconn | dathasloginevt | datconnlimit | datfrozenxid | datminmxid | dattablespace | datcollate  |  datctype   | datlocale | daticurules | datcollversion |                         datacl                         
-----+-----------+--------+----------+----------------+---------------+--------------+----------------+--------------+--------------+------------+---------------+-------------+-------------+-----------+-------------+----------------+--------------------------------------------------------
   1 | template1 |     10 |        6 | c              | t             | t            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=c/postgres,postgres=CTc/postgres}
   4 | template0 |     10 |        6 | c              | t             | f            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             |                | {=c/postgres,postgres=CTc/postgres}
   5 | postgres  |     10 |        6 | c              | f             | t            | f              |           -1 |          731 |          1 |          1663 | en_US.UTF-8 | en_US.UTF-8 |           |             | 2.28           | {=Tc/postgres,postgres=CTc/postgres,test=CTc/postgres}
(3 rows)

postgres=#

上述字段的定义及解释,如下:

// src/include/catalog/pg_database.h

...
	/* database has login event triggers? */
	bool		dathasloginevt;
...

接下来再创建一个login的事件触发器,内部元数据信息 如下:

postgres=# CREATE OR REPLACE FUNCTION login_validate() RETURNS event_trigger LANGUAGE plpgsql AS $$
postgres$# DECLARE
postgres$# BEGIN
postgres$#     IF '' = current_setting('application_name') THEN
postgres$#         raise exception 'You don''t have application_name set!';
postgres$#     END IF;
postgres$#     raise log 'user logged with application name: %', current_setting('application_name');
postgres$# END;
postgres$# $$;
CREATE FUNCTION
postgres=# 
postgres=# CREATE EVENT TRIGGER login_validate
postgres-#   ON login
postgres-#   EXECUTE FUNCTION login_validate();
CREATE EVENT TRIGGER
postgres=# 
postgres=# select datname,dathasloginevt from pg_database;
  datname  | dathasloginevt 
-----------+----------------
 template1 | f
 template0 | f
 postgres  | t
(3 rows)

postgres=# select * from pg_trigger ;
 oid | tgrelid | tgparentid | tgname | tgfoid | tgtype | tgenabled | tgisinternal | tgconstrrelid | tgconstrindid | tgconstraint | tgdeferrable | tginitdeferred | tgnargs | tgattr | tgargs | tgqual | tgoldtable | tgnewtable 
-----+---------+------------+--------+--------+--------+-----------+--------------+---------------+---------------+--------------+--------------+----------------+---------+--------+--------+--------+------------+------------
(0 rows)

postgres=# 
postgres=# select * from pg_event_trigger ;
  oid  |    evtname     | evtevent | evtowner | evtfoid | evtenabled | evttags 
-------+----------------+----------+----------+---------+------------+---------
 16394 | login_validate | login    |       10 |   16393 | O          | 
(1 row)

postgres=#

image

下面首先看一下函数SetDatatabaseHasLoginEventTriggers,如下:

// src/backend/commands/event_trigger.c

/*
 * Set pg_database.dathasloginevt flag for current database indicating that
 * current database has on login event triggers.
 * 
 * 为当前数据库设置 pg_database.dathasloginevt 标志,指示当前数据库具有登录事件触发器
 */
void
SetDatatabaseHasLoginEventTriggers(void)
{
	/* Set dathasloginevt flag in pg_database */
	Form_pg_database db;
	Relation	pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
	HeapTuple	tuple;

	/*
	 * Use shared lock to prevent a conflict with EventTriggerOnLogin() trying
	 * to reset pg_database.dathasloginevt flag.  Note, this lock doesn't
	 * effectively blocks database or other objection.  It's just custom lock
	 * tag used to prevent multiple backends changing
	 * pg_database.dathasloginevt flag.
	 * 
	 * 使用共享锁来防止与尝试重置 pg_database.dathasloginevt 标志的 EventTriggerOnLogin() 发生冲突
	 * 请注意,此锁不会有效地阻止数据库或其他异议
	 * 它只是用于防止多个后端更改 pg_database.dathasloginevt 标志的自定义锁定标记
	 */
	LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, AccessExclusiveLock);

	tuple = SearchSysCacheCopy1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
	if (!HeapTupleIsValid(tuple))
		elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
	db = (Form_pg_database) GETSTRUCT(tuple);
	if (!db->dathasloginevt)
	{
		db->dathasloginevt = true;
		CatalogTupleUpdate(pg_db, &tuple->t_self, tuple);
		CommandCounterIncrement();
	}
	table_close(pg_db, RowExclusiveLock);
	heap_freetuple(tuple);
}

下图是当新连接建立之后的函数调用,如下:

image

// src/backend/commands/event_trigger.c

/*
 * Fire login event triggers if any are present.  The dathasloginevt
 * pg_database flag is left unchanged when an event trigger is dropped to avoid
 * complicating the codepath in the case of multiple event triggers.  This
 * function will instead unset the flag if no trigger is defined.
 *
 * 如果存在,则触发登录事件
 * 当事件触发器被删除时,dathasloginevt pg_database 标志保持不变,以避免在多个事件触发器的情况下使代码路径复杂化
 * 如果没有定义触发器,此函数将取消设置该标志
 */
void
EventTriggerOnLogin(void)
{
	List	   *runlist;
	EventTriggerData trigdata;

	/*
	 * See EventTriggerDDLCommandStart for a discussion about why event
	 * triggers are disabled in single user mode or via a GUC.  We also need a
	 * database connection (some background workers don't have it).
	 *
	 * 有关为何在单用户模式下或通过 GUC 禁用事件触发器的讨论,请参阅 EventTriggerDDLCommandStart
	 * 我们还需要一个数据库连接(一些后台工作人员没有它)
	 */
	if (!IsUnderPostmaster || !event_triggers ||
		!OidIsValid(MyDatabaseId) || !MyDatabaseHasLoginEventTriggers)
		return;

	StartTransactionCommand();
	runlist = EventTriggerCommonSetup(NULL,
									  EVT_Login, "login",
									  &trigdata, false);

	if (runlist != NIL)
	{
		/*
		 * Event trigger execution may require an active snapshot.
		 * 事件触发器执行可能需要活动快照
		 */
		PushActiveSnapshot(GetTransactionSnapshot());

		/* Run the triggers. */
		// 运行触发器
		EventTriggerInvoke(runlist, &trigdata);

		/* Cleanup. */
		list_free(runlist);

		PopActiveSnapshot();
	}

	/*
	 * There is no active login event trigger, but our
	 * pg_database.dathasloginevt is set. Try to unset this flag.  We use the
	 * lock to prevent concurrent SetDatatabaseHasLoginEventTriggers(), but we
	 * don't want to hang the connection waiting on the lock.  Thus, we are
	 * just trying to acquire the lock conditionally.
	 *
	 * 没有活动的登录事件触发器,但我们的 pg_database.dathasloginevt 已设置
	 * 尝试取消设置此标志
	 * 我们使用锁来防止并发 SetDatatabaseHasLoginEventTriggers(),但我们不想挂起等待锁的连接
	 * 因此,我们只是尝试有条件地获取锁
	 */
	else if (ConditionalLockSharedObject(DatabaseRelationId, MyDatabaseId,
										 0, AccessExclusiveLock))
	{
		/*
		 * The lock is held.  Now we need to recheck that login event triggers
		 * list is still empty.  Once the list is empty, we know that even if
		 * there is a backend which concurrently inserts/enables a login event
		 * trigger, it will update pg_database.dathasloginevt *afterwards*.
		 * 
		 * 锁被持有
		 * 现在我们需要重新检查登录事件触发器列表是否仍然为空
		 * 一旦列表为空,我们知道即使有一个后端同时插入/启用登录事件触发器,它也会更新 pg_database.dathasloginevt *之后*
		 */
		runlist = EventTriggerCommonSetup(NULL,
										  EVT_Login, "login",
										  &trigdata, true);

		if (runlist == NIL)
		{
			Relation	pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
			HeapTuple	tuple;
			Form_pg_database db;
			ScanKeyData key[1];
			SysScanDesc scan;

			/*
			 * Get the pg_database tuple to scribble on.  Note that this does
			 * not directly rely on the syscache to avoid issues with
			 * flattened toast values for the in-place update.
			 *
			 * 获取要在其上书写的 pg_database 元组
			 * 请注意,这并不直接依赖于 syscache 来避免就地更新出现扁平化 toast 值的问题
			 */
			ScanKeyInit(&key[0],
						Anum_pg_database_oid,
						BTEqualStrategyNumber, F_OIDEQ,
						ObjectIdGetDatum(MyDatabaseId));

			scan = systable_beginscan(pg_db, DatabaseOidIndexId, true,
									  NULL, 1, key);
			tuple = systable_getnext(scan);
			tuple = heap_copytuple(tuple);
			systable_endscan(scan);

			if (!HeapTupleIsValid(tuple))
				elog(ERROR, "could not find tuple for database %u", MyDatabaseId);

			db = (Form_pg_database) GETSTRUCT(tuple);
			if (db->dathasloginevt)
			{
				db->dathasloginevt = false;

				/*
				 * Do an "in place" update of the pg_database tuple.  Doing
				 * this instead of regular updates serves two purposes. First,
				 * that avoids possible waiting on the row-level lock. Second,
				 * that avoids dealing with TOAST.
				 * 对 pg_database 元组进行“就地”更新。这样做而不是定期更新有两个目的
				 * 首先,这避免了可能等待行级锁。其次,这避免了处理 TOAST
				 *
				 * It's known that changes made by heap_inplace_update() may
				 * be lost due to concurrent normal updates.  However, we are
				 * OK with that.  The subsequent connections will still have a
				 * chance to set "dathasloginevt" to false.
				 * 众所周知,heap_inplace_update() 所做的更改可能会由于并发的正常更新而丢失
				 * 不过,我们对此表示同意\
				 * 后续连接仍有机会将“dathasloginevt”设置为 false
				 */
				heap_inplace_update(pg_db, tuple);
			}
			table_close(pg_db, RowExclusiveLock);
			heap_freetuple(tuple);
		}
		else
		{
			list_free(runlist);
		}
	}
	CommitTransactionCommand();
}

开始调试,如下:

## 附加的是 postmaster

{
    // 使用 IntelliSense 了解相关属性。 
    // 悬停以查看现有属性的描述。
    // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "(gdb) 附加",
            "type": "cppdbg",
            "request": "attach",
            "program": "/home/postgres/test/bin/postgres",
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "将反汇编风格设置为 Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                },
                {
                    "text": "-gdb-set follow-fork-mode child"
                }
            ]
        }
    ]
}

此时的函数堆栈,如下:

EventTriggerOnLogin() (\home\postgres\postgres\src\backend\commands\event_trigger.c:900)
PostgresMain(const char * dbname, const char * username) (\home\postgres\postgres\src\backend\tcop\postgres.c:4315)
BackendMain(char * startup_data, size_t startup_data_len) (\home\postgres\postgres\src\backend\tcop\backend_startup.c:105)
postmaster_child_launch(BackendType child_type, char * startup_data, size_t startup_data_len, ClientSocket * client_sock) (\home\postgres\postgres\src\backend\postmaster\launch_backend.c:265)
BackendStartup(ClientSocket * client_sock) (\home\postgres\postgres\src\backend\postmaster\postmaster.c:3593)
ServerLoop() (\home\postgres\postgres\src\backend\postmaster\postmaster.c:1674)
PostmasterMain(int argc, char ** argv) (\home\postgres\postgres\src\backend\postmaster\postmaster.c:1372)
main(int argc, char ** argv) (\home\postgres\postgres\src\backend\main\main.c:197)

image

如上图所示:

  1. MyDatabaseHasLoginEventTriggers 为真,该值的更新 如下图所示
  2. runlist 链表中有一个元素

image


接下来就去执行这个触发器,如下:

// src/backend/commands/event_trigger.c

/*
 * Invoke each event trigger in a list of event triggers.
 */
static void
EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
{
	MemoryContext context;
	MemoryContext oldcontext;
	ListCell   *lc;
	bool		first = true;

	/* Guard against stack overflow due to recursive event trigger */
	check_stack_depth();

	/*
	 * Let's evaluate event triggers in their own memory context, so that any
	 * leaks get cleaned up promptly.
	 */
	context = AllocSetContextCreate(CurrentMemoryContext,
									"event trigger context",
									ALLOCSET_DEFAULT_SIZES);
	oldcontext = MemoryContextSwitchTo(context);

	/* Call each event trigger. */
	foreach(lc, fn_oid_list)
	{
		LOCAL_FCINFO(fcinfo, 0);
		Oid			fnoid = lfirst_oid(lc);
		FmgrInfo	flinfo;
		PgStat_FunctionCallUsage fcusage;

		elog(DEBUG1, "EventTriggerInvoke %u", fnoid);

		/*
		 * We want each event trigger to be able to see the results of the
		 * previous event trigger's action.  Caller is responsible for any
		 * command-counter increment that is needed between the event trigger
		 * and anything else in the transaction.
		 */
		if (first)
			first = false;
		else
			CommandCounterIncrement();

		/* Look up the function */
		fmgr_info(fnoid, &flinfo);

		/* Call the function, passing no arguments but setting a context. */
		InitFunctionCallInfoData(*fcinfo, &flinfo, 0,
								 InvalidOid, (Node *) trigdata, NULL);
		pgstat_init_function_usage(fcinfo, &fcusage);
		FunctionCallInvoke(fcinfo);
		pgstat_end_function_usage(&fcusage, true);

		/* Reclaim memory. */
		MemoryContextReset(context);
	}

	/* Restore old memory context and delete the temporary one. */
	MemoryContextSwitchTo(oldcontext);
	MemoryContextDelete(context);
}

image

postgres=# \x
Expanded display is on.
postgres=# 
postgres=# select * from pg_proc where oid = 16388;
-[ RECORD 1 ]---+-------------------------------------------------------------------------------------------
oid             | 16388
proname         | login_validate
pronamespace    | 2200
proowner        | 10
prolang         | 13553
procost         | 100
prorows         | 0
provariadic     | 0
prosupport      | -
prokind         | f
prosecdef       | f
proleakproof    | f
proisstrict     | f
proretset       | f
provolatile     | v
proparallel     | u
pronargs        | 0
pronargdefaults | 0
prorettype      | 3838
proargtypes     | 
proallargtypes  | 
proargmodes     | 
proargnames     | 
proargdefaults  | 
protrftypes     | 
prosrc          |                                                                                           +
                | DECLARE                                                                                   +
                | BEGIN                                                                                     +
                |     IF '' = current_setting('application_name') THEN                                      +
                |         raise exception 'You don''t have application_name set!';                          +
                |     END IF;                                                                               +
                |     raise log 'user logged with application name: %', current_setting('application_name');+
                | END;                                                                                      +
                | 
probin          | 
prosqlbody      | 
proconfig       | 
proacl          | 

postgres=# \x
Expanded display is off.
postgres=#
posted @ 2024-04-11 17:57  孤傲小二~阿沐  阅读(40)  评论(0)    收藏  举报