From 5e0e9817cae754bb28f786ecf2435f459a5e7e81 Mon Sep 17 00:00:00 2001 From: Daniel Gustafsson Date: Mon, 14 Mar 2022 14:27:10 +0100 Subject: [PATCH v29 2/2] Add a new "login" event and login event trigger support. The login event occurs when a user logs in to the system. Author: Konstantin Knizhnik and Greg Nancarrow Discussion: https://postgr.es/m/0d46d29f-4558-3af9-9c85-7774e14a7709%40postgrespro.ru --- doc/src/sgml/bki.sgml | 2 +- doc/src/sgml/catalogs.sgml | 13 ++ doc/src/sgml/ecpg.sgml | 2 + doc/src/sgml/event-trigger.sgml | 81 ++++++++- doc/src/sgml/ref/create_event_trigger.sgml | 2 +- src/backend/commands/dbcommands.c | 1 + src/backend/commands/event_trigger.c | 156 +++++++++++++++++- src/backend/tcop/postgres.c | 4 + src/backend/utils/cache/evtcache.c | 2 + src/backend/utils/misc/guc.c | 1 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/bin/pg_dump/pg_dump.c | 5 + src/bin/psql/tab-complete.c | 3 +- src/include/catalog/pg_database.dat | 2 +- src/include/catalog/pg_database.h | 3 + src/include/commands/event_trigger.h | 4 +- src/include/tcop/cmdtaglist.h | 1 + src/include/utils/evtcache.h | 3 +- src/test/recovery/t/001_stream_rep.pl | 23 +++ src/test/regress/expected/event_trigger.out | 38 +++++ src/test/regress/sql/event_trigger.sql | 24 +++ 21 files changed, 359 insertions(+), 12 deletions(-) diff --git a/doc/src/sgml/bki.sgml b/doc/src/sgml/bki.sgml index 33955494c6..caef1df200 100644 --- a/doc/src/sgml/bki.sgml +++ b/doc/src/sgml/bki.sgml @@ -183,7 +183,7 @@ { oid => '1', oid_symbol => 'TemplateDbOid', descr => 'database\'s default template', datname => 'template1', encoding => 'ENCODING', datistemplate => 't', - datallowconn => 't', datconnlimit => '-1', datfrozenxid => '0', + datallowconn => 't', dathasloginevt => 'f', datconnlimit => '-1', datfrozenxid => '0', datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', datacl => '_null_' }, diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 2a8cd02664..4dd9039b8a 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -2982,6 +2982,19 @@ SCRAM-SHA-256$<iteration count>:&l + + + dathasloginevt bool + + + Indicates that there are login event triggers defined for this database. + This flag is used to avoid extra lookups on the + pg_event_trigger table during each backend + startup. This flag is used internally by PostgreSQL + and should not be manually altered or read for monitoring purposes. + + + datconnlimit int4 diff --git a/doc/src/sgml/ecpg.sgml b/doc/src/sgml/ecpg.sgml index cdc4761c60..197b476205 100644 --- a/doc/src/sgml/ecpg.sgml +++ b/doc/src/sgml/ecpg.sgml @@ -4731,6 +4731,7 @@ datdba = 10 (type: 1) encoding = 0 (type: 5) datistemplate = t (type: 1) datallowconn = t (type: 1) +dathasloginevt = f (type: 1) datconnlimit = -1 (type: 5) datfrozenxid = 379 (type: 1) dattablespace = 1663 (type: 1) @@ -4755,6 +4756,7 @@ datdba = 10 (type: 1) encoding = 0 (type: 5) datistemplate = f (type: 1) datallowconn = t (type: 1) +dathasloginevt = f (type: 1) datconnlimit = -1 (type: 5) datfrozenxid = 379 (type: 1) dattablespace = 1663 (type: 1) diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index 9c66f97b0f..b568f3b2aa 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -28,6 +28,7 @@ An event trigger fires whenever the event with which it is associated occurs in the database in which it is defined. Currently, the only supported events are + login, ddl_command_start, ddl_command_end, table_rewrite @@ -35,6 +36,18 @@ Support for additional events may be added in future releases. + + The login event occurs when a user logs in to the + system. + Any bugs in a trigger procedure for this event may prevent successful + login to the system. Such bugs may be fixed after first reconnecting to + the system with event triggers disabled (see ) + or using restarting the system in single-user mode (as event triggers are + disabled in this mode). + See the reference page for details about + using single-user mode. + + The ddl_command_start event occurs just before the execution of a CREATE, ALTER, DROP, @@ -1156,7 +1169,7 @@ typedef struct EventTriggerData - A Complete Event Trigger Example + A C language Event Trigger Example Here is a very simple example of an event trigger function written in C. @@ -1296,6 +1309,72 @@ $$; CREATE EVENT TRIGGER no_rewrite_allowed ON table_rewrite EXECUTE FUNCTION no_rewrite(); + + + + + + A Database Login Event Trigger Example + + + The event trigger on the login event can be + useful for logging user logins, for verifying the connection and + assigning roles according to current circumstances, or for some session + data initialization. It is vital that any event trigger using the + login event checks whether or not the database is in + recovery. + + + + The following example demonstrates these options. + +-- create test tables and roles +CREATE TABLE user_login_log ( + "user" text, + "session_start" timestamp with time zone +); +CREATE ROLE day_worker; +CREATE ROLE night_worker; + +-- the example trigger function +CREATE OR REPLACE FUNCTION init_session() + RETURNS event_trigger SECURITY DEFINER + LANGUAGE plpgsql AS +$$ +DECLARE + hour integer = EXTRACT('hour' FROM current_time); + rec boolean; +BEGIN +-- Ensure the database is not in recovery +SELECT pg_is_in_recovery() INTO rec; +IF rec THEN + RETURN; +END IF + +-- 1) Assign some roles +IF hour BETWEEN 8 AND 20 THEN -- at daytime grant the day_worker role + EXECUTE 'REVOKE night_worker FROM ' || quote_ident(session_user); + EXECUTE 'GRANT day_worker TO ' || quote_ident(session_user); +ELSIF hour BETWEEN 2 AND 4 THEN + RAISE EXCEPTION 'Login forbidden'; -- do not allow to login these hours +ELSE -- at other time grant the night_worker role + EXECUTE 'REVOKE day_worker FROM ' || quote_ident(session_user); + EXECUTE 'GRANT night_worker TO ' || quote_ident(session_user); +END IF; + +-- 2) Initialize some user session data +CREATE TEMP TABLE session_storage (x float, y integer); + +-- 3) Log the connection time +INSERT INTO user_login_log VALUES (session_user, current_timestamp); + +END; +$$; + +-- trigger definition +CREATE EVENT TRIGGER init_session + ON login + EXECUTE FUNCTION init_session(); diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml index 1a78555c64..92c376b8e7 100644 --- a/doc/src/sgml/ref/create_event_trigger.sgml +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -125,7 +125,7 @@ CREATE EVENT TRIGGER name database so much that you can't even drop the trigger, restart in single-user mode and you'll be able to do that. Even triggers can also be temporarily disabled for such troubleshooting, see - . + . diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 623e5ec778..08871ec5e0 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -712,6 +712,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt) new_record[Anum_pg_database_datlocprovider - 1] = CharGetDatum(dblocprovider); new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(dbistemplate); new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(dballowconnections); + new_record[Anum_pg_database_dathasloginevt - 1] = BoolGetDatum(false); new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid); diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 70995cfc49..4e2e6629bd 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -20,6 +20,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" +#include "catalog/pg_database.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_namespace.h" #include "catalog/pg_opclass.h" @@ -37,15 +38,18 @@ #include "miscadmin.h" #include "parser/parse_func.h" #include "pgstat.h" +#include "storage/lmgr.h" #include "tcop/deparse_utility.h" #include "tcop/utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/evtcache.h" #include "utils/fmgroids.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" typedef struct EventTriggerQueryState @@ -134,6 +138,7 @@ CreateEventTrigger(CreateEventTrigStmt *stmt) if (strcmp(stmt->eventname, "ddl_command_start") != 0 && strcmp(stmt->eventname, "ddl_command_end") != 0 && strcmp(stmt->eventname, "sql_drop") != 0 && + strcmp(stmt->eventname, "login") != 0 && strcmp(stmt->eventname, "table_rewrite") != 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -297,6 +302,30 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO CatalogTupleInsert(tgrel, tuple); heap_freetuple(tuple); + /* + * Login event triggers have an additional flag in pg_database to allow + * faster lookups in hot codepaths. Set the flag unless already True. + */ + if (strcmp(eventname, "login") == 0) + { + Form_pg_database db; + Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock); + + /* Set dathasloginevt flag in pg_database */ + 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); + } + /* Depend on owner. */ recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); @@ -584,10 +613,15 @@ EventTriggerCommonSetup(Node *parsetree, { CommandTag dbgtag; - dbgtag = CreateCommandTag(parsetree); + if (event == EVT_Login) + dbgtag = CMDTAG_LOGIN; + else + dbgtag = CreateCommandTag(parsetree); + if (event == EVT_DDLCommandStart || event == EVT_DDLCommandEnd || - event == EVT_SQLDrop) + event == EVT_SQLDrop || + event == EVT_Login) { if (!command_tag_event_trigger_ok(dbgtag)) elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); @@ -606,7 +640,10 @@ EventTriggerCommonSetup(Node *parsetree, return NIL; /* Get the command tag. */ - tag = CreateCommandTag(parsetree); + if (event == EVT_Login) + tag = CMDTAG_LOGIN; + else + tag = CreateCommandTag(parsetree); /* * Filter list of event triggers by command tag, and copy them into our @@ -807,6 +844,112 @@ EventTriggerSQLDrop(Node *parsetree) list_free(runlist); } +/* + * Return true if this database has login event triggers, false otherwise. + */ +static bool +DatabaseHasLoginEventTriggers(void) +{ + bool has_login_event_triggers; + HeapTuple tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId)); + + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + + has_login_event_triggers = ((Form_pg_database) GETSTRUCT(tuple))->dathasloginevt; + ReleaseSysCache(tuple); + return has_login_event_triggers; +} + +/* + * Fire login event triggers if any are present. The dathasloginevt + * pg_database flag is left 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. + */ +void +EventTriggerOnLogin(void) +{ + List *runlist; + EventTriggerData trigdata; + + /* + * See EventTriggerDDLCommandStart for a discussion about why event + * triggers are disabled in single user mode. + */ + if (!IsUnderPostmaster || !OidIsValid(MyDatabaseId) + || ignore_event_trigger_check(EVT_Login)) + return; + + StartTransactionCommand(); + + if (DatabaseHasLoginEventTriggers()) + { + runlist = EventTriggerCommonSetup(NULL, + EVT_Login, "login", + &trigdata); + + if (runlist != NIL) + { + /* + * Event trigger execution may require an active snapshot. + */ + PushActiveSnapshot(GetTransactionSnapshot()); + + /* Run the triggers. */ + EventTriggerInvoke(runlist, &trigdata); + + /* Cleanup. */ + list_free(runlist); + + PopActiveSnapshot(); + } + else + { + /* + * Runlist is empty: clear dathasloginevt flag + */ + Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock); + HeapTuple tuple; + Form_pg_database db; + + 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) + { + /* + * There can be a race condition: a login event trigger may + * have been added after the pg_event_trigger table was + * scanned, and we don't want to erroneously clear the + * dathasloginevt flag in this case. To be sure that this + * hasn't happened, repeat the scan under the pg_database + * table lock. + */ + runlist = EventTriggerCommonSetup(NULL, + EVT_Login, "login", + &trigdata); + if (runlist == NIL) /* list is still empty, so clear the + * flag */ + { + db->dathasloginevt = false; + CatalogTupleUpdate(pg_db, &tuple->t_self, tuple); + } + else + list_free(runlist); + } + table_close(pg_db, RowExclusiveLock); + heap_freetuple(tuple); + } + } + CommitTransactionCommand(); +} + /* * Fire table_rewrite triggers. @@ -2186,8 +2329,7 @@ stringify_adefprivs_objtype(ObjectType objtype) /* * Checks whether the specified event is ignored by the ignore_event_trigger - * GUC or not. Currently, the GUC only supports ignoreing all or nothing but - * that will most likely change so the function takes an event to aid that. + * GUC or not. */ static bool ignore_event_trigger_check(EventTriggerEvent event) @@ -2197,6 +2339,10 @@ ignore_event_trigger_check(EventTriggerEvent event) if (ignore_event_trigger == IGNORE_EVENT_TRIGGER_ALL) return true; + if (ignore_event_trigger == IGNORE_EVENT_TRIGGER_LOGIN + && event == EVT_Login) + return true; + /* IGNORE_EVENT_TRIGGER_NONE */ return false; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index ba2fcfeb4a..5638cd95b2 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -41,6 +41,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/async.h" +#include "commands/event_trigger.h" #include "commands/prepare.h" #include "common/pg_prng.h" #include "jit/jit.h" @@ -4202,6 +4203,9 @@ PostgresMain(const char *dbname, const char *username) initStringInfo(&row_description_buf); MemoryContextSwitchTo(TopMemoryContext); + /* Fire any defined login event triggers, if appropriate */ + EventTriggerOnLogin(); + /* * POSTGRES main processing loop begins here * diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c index 3a9c9f0c50..43a38ae6c7 100644 --- a/src/backend/utils/cache/evtcache.c +++ b/src/backend/utils/cache/evtcache.c @@ -167,6 +167,8 @@ BuildEventTriggerCache(void) event = EVT_SQLDrop; else if (strcmp(evtevent, "table_rewrite") == 0) event = EVT_TableRewrite; + else if (strcmp(evtevent, "login") == 0) + event = EVT_Login; else continue; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 088c4690d9..c5d53386e7 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -569,6 +569,7 @@ static const struct config_enum_entry wal_compression_options[] = { static const struct config_enum_entry ignore_event_trigger_options[] = { {"none", IGNORE_EVENT_TRIGGER_NONE, false}, {"all", IGNORE_EVENT_TRIGGER_ALL, false}, + {"login", IGNORE_EVENT_TRIGGER_LOGIN, false}, {NULL, 0, false} }; diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 4cf5b26a36..0d7623fc7a 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -674,6 +674,7 @@ # error #search_path = '"$user", public' # schema names #row_security = on +#ignore_event_trigger = 'none' #default_table_access_method = 'heap' #default_tablespace = '' # a tablespace name, '' uses the default #default_toast_compression = 'pglz' # 'pglz' or 'lz4' diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e5816c4cce..dd756ae4db 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -3052,6 +3052,11 @@ dumpDatabase(Archive *fout) appendPQExpBufferStr(delQry, ";\n"); } + /* + * We do not restore pg_database.dathasloginevt because it is set + * automatically on login event trigger creation. + */ + /* Add database-specific SET options */ dumpDatabaseConfig(fout, creaQry, datname, dbCatId.oid); diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 5c064595a9..8850324a3b 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -3395,7 +3395,8 @@ psql_completion(const char *text, int start, int end) COMPLETE_WITH("ON"); /* Complete CREATE EVENT TRIGGER ON with event_type */ else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON")) - COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop"); + COMPLETE_WITH("ddl_command_start", "ddl_command_end", + "login", "sql_drop"); /* * Complete CREATE EVENT TRIGGER ON . EXECUTE FUNCTION diff --git a/src/include/catalog/pg_database.dat b/src/include/catalog/pg_database.dat index 5feedff7bf..78402d48bc 100644 --- a/src/include/catalog/pg_database.dat +++ b/src/include/catalog/pg_database.dat @@ -15,7 +15,7 @@ { oid => '1', oid_symbol => 'TemplateDbOid', descr => 'default template for new databases', datname => 'template1', encoding => 'ENCODING', datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't', - datallowconn => 't', datconnlimit => '-1', datfrozenxid => '0', + datallowconn => 't', dathasloginevt => 'f', datconnlimit => '-1', datfrozenxid => '0', datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE', datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' }, diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h index a9f4a8071f..55d7ae7988 100644 --- a/src/include/catalog/pg_database.h +++ b/src/include/catalog/pg_database.h @@ -49,6 +49,9 @@ CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID /* new connections allowed? */ bool datallowconn; + /* database has login event triggers? */ + bool dathasloginevt; + /* max connections allowed (-1=no limit) */ int32 datconnlimit; diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 9e7671e63f..2400cc7d7a 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -32,7 +32,8 @@ typedef struct EventTriggerData typedef enum ignore_event_trigger_events { IGNORE_EVENT_TRIGGER_NONE, - IGNORE_EVENT_TRIGGER_ALL + IGNORE_EVENT_TRIGGER_ALL, + IGNORE_EVENT_TRIGGER_LOGIN } IgnoreEventTriggersEvents; extern int ignore_event_trigger; @@ -62,6 +63,7 @@ extern void EventTriggerDDLCommandStart(Node *parsetree); extern void EventTriggerDDLCommandEnd(Node *parsetree); extern void EventTriggerSQLDrop(Node *parsetree); extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason); +extern void EventTriggerOnLogin(void); extern bool EventTriggerBeginCompleteQuery(void); extern void EventTriggerEndCompleteQuery(void); diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h index 4bc7ddf410..d6e408e1ed 100644 --- a/src/include/tcop/cmdtaglist.h +++ b/src/include/tcop/cmdtaglist.h @@ -186,6 +186,7 @@ PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true) PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false) PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false) PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false) +PG_CMDTAG(CMDTAG_LOGIN, "LOGIN", true, false, false) PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true) PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false) PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false) diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h index ddb67a68fa..a66344aacb 100644 --- a/src/include/utils/evtcache.h +++ b/src/include/utils/evtcache.h @@ -22,7 +22,8 @@ typedef enum EVT_DDLCommandStart, EVT_DDLCommandEnd, EVT_SQLDrop, - EVT_TableRewrite + EVT_TableRewrite, + EVT_Login, } EventTriggerEvent; typedef struct diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl index 583ee87da8..c3dbd75af3 100644 --- a/src/test/recovery/t/001_stream_rep.pl +++ b/src/test/recovery/t/001_stream_rep.pl @@ -46,6 +46,24 @@ $node_standby_2->start; $node_primary->safe_psql('postgres', "CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a"); +$node_primary->safe_psql('postgres', q{ +CREATE TABLE user_logins(id serial, who text); + +CREATE FUNCTION on_login_proc() RETURNS EVENT_TRIGGER AS $$ +BEGIN + IF NOT pg_is_in_recovery() THEN + INSERT INTO user_logins (who) VALUES (session_user); + END IF; + IF session_user = 'regress_hacker' THEN + RAISE EXCEPTION 'You are not welcome!'; + END IF; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +CREATE EVENT TRIGGER on_login_trigger ON login EXECUTE FUNCTION on_login_proc(); +ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS; +}); + # Wait for standbys to catch up my $primary_lsn = $node_primary->lsn('write'); $node_primary->wait_for_catchup($node_standby_1, 'replay', $primary_lsn); @@ -387,6 +405,11 @@ sub replay_check replay_check(); +my $evttrig = $node_standby_1->safe_psql('postgres', "SELECT evtname FROM pg_event_trigger WHERE evtevent = 'login'"); +is($evttrig, 'on_login_trigger', 'Name of login trigger'); +$evttrig = $node_standby_2->safe_psql('postgres', "SELECT evtname FROM pg_event_trigger WHERE evtevent = 'login'"); +is($evttrig, 'on_login_trigger', 'Name of login trigger'); + note "enabling hot_standby_feedback"; # Enable hs_feedback. The slot should gain an xmin. We set the status interval diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 44d545de25..fb2c799ebb 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -604,3 +604,41 @@ SELECT DROP EVENT TRIGGER start_rls_command; DROP EVENT TRIGGER end_rls_command; DROP EVENT TRIGGER sql_drop_command; +-- Login event triggers +CREATE TABLE user_logins(id serial, who text); +GRANT SELECT ON user_logins TO public; +CREATE FUNCTION on_login_proc() RETURNS event_trigger AS $$ +BEGIN + INSERT INTO user_logins (who) VALUES (SESSION_USER); + RAISE NOTICE 'You are welcome!'; +END; +$$ LANGUAGE plpgsql; +CREATE EVENT TRIGGER on_login_trigger ON login EXECUTE PROCEDURE on_login_proc(); +ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS; +\c +NOTICE: You are welcome! +SELECT COUNT(*) FROM user_logins; + count +------- + 1 +(1 row) + +\c +NOTICE: You are welcome! +SELECT COUNT(*) FROM user_logins; + count +------- + 2 +(1 row) + +-- Check dathasloginevt in system catalog +SELECT dathasloginevt FROM pg_database WHERE datname= :'DBNAME'; + dathasloginevt +---------------- + t +(1 row) + +-- Cleanup +DROP TABLE user_logins; +DROP EVENT TRIGGER on_login_trigger; +DROP FUNCTION on_login_proc(); diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index 1446cf8cc8..3123cbb23d 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -462,3 +462,27 @@ SELECT DROP EVENT TRIGGER start_rls_command; DROP EVENT TRIGGER end_rls_command; DROP EVENT TRIGGER sql_drop_command; + +-- Login event triggers +CREATE TABLE user_logins(id serial, who text); +GRANT SELECT ON user_logins TO public; +CREATE FUNCTION on_login_proc() RETURNS event_trigger AS $$ +BEGIN + INSERT INTO user_logins (who) VALUES (SESSION_USER); + RAISE NOTICE 'You are welcome!'; +END; +$$ LANGUAGE plpgsql; +CREATE EVENT TRIGGER on_login_trigger ON login EXECUTE PROCEDURE on_login_proc(); +ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS; +\c +SELECT COUNT(*) FROM user_logins; +\c +SELECT COUNT(*) FROM user_logins; + +-- Check dathasloginevt in system catalog +SELECT dathasloginevt FROM pg_database WHERE datname= :'DBNAME'; + +-- Cleanup +DROP TABLE user_logins; +DROP EVENT TRIGGER on_login_trigger; +DROP FUNCTION on_login_proc(); -- 2.24.3 (Apple Git-128)