mirror of
https://github.com/postgres/postgres.git
synced 2025-07-21 00:01:45 -04:00
Compare commits
7 Commits
5f27b5f848
...
1f89b73c4e
Author | SHA1 | Date | |
---|---|---|---|
|
1f89b73c4e | ||
|
47ab5d2e2e | ||
|
20b7708662 | ||
|
e9718b4bd3 | ||
|
01529c7040 | ||
|
e83d1b0c40 | ||
|
c558e6fd92 |
@ -14,6 +14,15 @@
|
|||||||
#
|
#
|
||||||
# $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso
|
# $ git log --pretty=format:"%H # %cd%n# %s" $PGINDENTGITHASH -1 --date=iso
|
||||||
|
|
||||||
|
e9718b4bd3e4234ffd5a4907a903367fc483c843 # 2023-10-16 09:36:31 +0900
|
||||||
|
# Fix code indentation violations in e83d1b0c40cc
|
||||||
|
|
||||||
|
01529c7040088db2718628d0814058598152bd39 # 2023-10-16 13:32:41 +1300
|
||||||
|
# Fix comment from commit 22655aa231.
|
||||||
|
|
||||||
|
b6a77c6a6ccf698787201b001cbbbf9c89fe5715 # 2023-10-11 17:14:31 -0400
|
||||||
|
# Reindent comment in GenericXLogFinish().
|
||||||
|
|
||||||
bc6041b61f6678d32a5cfb70744653cd8f8d01c0 # 2023-08-30 15:56:22 +0900
|
bc6041b61f6678d32a5cfb70744653cd8f8d01c0 # 2023-08-30 15:56:22 +0900
|
||||||
# Fix code indentation vioaltion introduced in commit 3c662643c4.
|
# Fix code indentation vioaltion introduced in commit 3c662643c4.
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@
|
|||||||
descr => 'database\'s default template',
|
descr => 'database\'s default template',
|
||||||
datname => 'template1', encoding => 'ENCODING',
|
datname => 'template1', encoding => 'ENCODING',
|
||||||
datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
|
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',
|
datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
|
||||||
datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },
|
datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE', datacl => '_null_' },
|
||||||
|
|
||||||
|
@ -3035,6 +3035,19 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
|
|||||||
</para></entry>
|
</para></entry>
|
||||||
</row>
|
</row>
|
||||||
|
|
||||||
|
<row>
|
||||||
|
<entry role="catalog_table_entry"><para role="column_definition">
|
||||||
|
<structfield>dathasloginevt</structfield> <type>bool</type>
|
||||||
|
</para>
|
||||||
|
<para>
|
||||||
|
Indicates that there are login event triggers defined for this database.
|
||||||
|
This flag is used to avoid extra lookups on the
|
||||||
|
<structname>pg_event_trigger</structname> table during each backend
|
||||||
|
startup. This flag is used internally by <productname>PostgreSQL</productname>
|
||||||
|
and should not be manually altered or read for monitoring purposes.
|
||||||
|
</para></entry>
|
||||||
|
</row>
|
||||||
|
|
||||||
<row>
|
<row>
|
||||||
<entry role="catalog_table_entry"><para role="column_definition">
|
<entry role="catalog_table_entry"><para role="column_definition">
|
||||||
<structfield>datconnlimit</structfield> <type>int4</type>
|
<structfield>datconnlimit</structfield> <type>int4</type>
|
||||||
|
@ -4769,6 +4769,7 @@ datdba = 10 (type: 1)
|
|||||||
encoding = 0 (type: 5)
|
encoding = 0 (type: 5)
|
||||||
datistemplate = t (type: 1)
|
datistemplate = t (type: 1)
|
||||||
datallowconn = t (type: 1)
|
datallowconn = t (type: 1)
|
||||||
|
dathasloginevt = f (type: 1)
|
||||||
datconnlimit = -1 (type: 5)
|
datconnlimit = -1 (type: 5)
|
||||||
datfrozenxid = 379 (type: 1)
|
datfrozenxid = 379 (type: 1)
|
||||||
dattablespace = 1663 (type: 1)
|
dattablespace = 1663 (type: 1)
|
||||||
@ -4793,6 +4794,7 @@ datdba = 10 (type: 1)
|
|||||||
encoding = 0 (type: 5)
|
encoding = 0 (type: 5)
|
||||||
datistemplate = f (type: 1)
|
datistemplate = f (type: 1)
|
||||||
datallowconn = t (type: 1)
|
datallowconn = t (type: 1)
|
||||||
|
dathasloginevt = f (type: 1)
|
||||||
datconnlimit = -1 (type: 5)
|
datconnlimit = -1 (type: 5)
|
||||||
datfrozenxid = 379 (type: 1)
|
datfrozenxid = 379 (type: 1)
|
||||||
dattablespace = 1663 (type: 1)
|
dattablespace = 1663 (type: 1)
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
An event trigger fires whenever the event with which it is associated
|
An event trigger fires whenever the event with which it is associated
|
||||||
occurs in the database in which it is defined. Currently, the only
|
occurs in the database in which it is defined. Currently, the only
|
||||||
supported events are
|
supported events are
|
||||||
|
<literal>login</literal>,
|
||||||
<literal>ddl_command_start</literal>,
|
<literal>ddl_command_start</literal>,
|
||||||
<literal>ddl_command_end</literal>,
|
<literal>ddl_command_end</literal>,
|
||||||
<literal>table_rewrite</literal>
|
<literal>table_rewrite</literal>
|
||||||
@ -35,6 +36,24 @@
|
|||||||
Support for additional events may be added in future releases.
|
Support for additional events may be added in future releases.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The <literal>login</literal> event occurs when an authenticated user logs
|
||||||
|
into the system. Any bug in a trigger procedure for this event may
|
||||||
|
prevent successful login to the system. Such bugs may be fixed by
|
||||||
|
setting <xref linkend="guc-event-triggers"/> is set to <literal>false</literal>
|
||||||
|
either in a connection string or configuration file. Alternative is
|
||||||
|
restarting the system in single-user mode (as event triggers are
|
||||||
|
disabled in this mode). See the <xref linkend="app-postgres"/> reference
|
||||||
|
page for details about using single-user mode.
|
||||||
|
The <literal>login</literal> event will also fire on standby servers.
|
||||||
|
To prevent servers from becoming inaccessible, such triggers must avoid
|
||||||
|
writing anything to the database when running on a standby.
|
||||||
|
Also, it's recommended to avoid long-running queries in
|
||||||
|
<literal>login</literal> event triggers. Notes that, for instance,
|
||||||
|
cancelling connection in <application>psql</application> wouldn't cancel
|
||||||
|
the in-progress <literal>login</literal> trigger.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
The <literal>ddl_command_start</literal> event occurs just before the
|
The <literal>ddl_command_start</literal> event occurs just before the
|
||||||
execution of a <literal>CREATE</literal>, <literal>ALTER</literal>, <literal>DROP</literal>,
|
execution of a <literal>CREATE</literal>, <literal>ALTER</literal>, <literal>DROP</literal>,
|
||||||
@ -1297,6 +1316,81 @@ $$;
|
|||||||
CREATE EVENT TRIGGER no_rewrite_allowed
|
CREATE EVENT TRIGGER no_rewrite_allowed
|
||||||
ON table_rewrite
|
ON table_rewrite
|
||||||
EXECUTE FUNCTION no_rewrite();
|
EXECUTE FUNCTION no_rewrite();
|
||||||
|
</programlisting>
|
||||||
|
</para>
|
||||||
|
</sect1>
|
||||||
|
|
||||||
|
<sect1 id="event-trigger-database-login-example">
|
||||||
|
<title>A Database Login Event Trigger Example</title>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The event trigger on the <literal>login</literal> event can be
|
||||||
|
useful for logging user logins, for verifying the connection and
|
||||||
|
assigning roles according to current circumstances, or for session
|
||||||
|
data initialization. It is very important that any event trigger using
|
||||||
|
the <literal>login</literal> event checks whether or not the database is
|
||||||
|
in recovery before performing any writes. Writing to a standby server
|
||||||
|
will make it inaccessible.
|
||||||
|
</para>
|
||||||
|
|
||||||
|
<para>
|
||||||
|
The following example demonstrates these options.
|
||||||
|
<programlisting>
|
||||||
|
-- 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 at time zone 'utc');
|
||||||
|
rec boolean;
|
||||||
|
BEGIN
|
||||||
|
-- 1. Forbid logging in between 2AM and 4AM.
|
||||||
|
IF hour BETWEEN 2 AND 4 THEN
|
||||||
|
RAISE EXCEPTION 'Login forbidden';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- The checks below cannot be performed on standby servers so
|
||||||
|
-- ensure the database is not in recovery before we perform any
|
||||||
|
-- operations.
|
||||||
|
SELECT pg_is_in_recovery() INTO rec;
|
||||||
|
IF rec THEN
|
||||||
|
RETURN;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 2. Assign some roles. At daytime, grant the day_worker role, else the
|
||||||
|
-- night_worker role.
|
||||||
|
IF hour BETWEEN 8 AND 20 THEN
|
||||||
|
EXECUTE 'REVOKE night_worker FROM ' || quote_ident(session_user);
|
||||||
|
EXECUTE 'GRANT day_worker TO ' || quote_ident(session_user);
|
||||||
|
ELSE
|
||||||
|
EXECUTE 'REVOKE day_worker FROM ' || quote_ident(session_user);
|
||||||
|
EXECUTE 'GRANT night_worker TO ' || quote_ident(session_user);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- 3. Initialize user session data
|
||||||
|
CREATE TEMP TABLE session_storage (x float, y integer);
|
||||||
|
ALTER TABLE session_storage OWNER TO session_user;
|
||||||
|
|
||||||
|
-- 4. Log the connection time
|
||||||
|
INSERT INTO public.user_login_log VALUES (session_user, current_timestamp);
|
||||||
|
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- trigger definition
|
||||||
|
CREATE EVENT TRIGGER init_session
|
||||||
|
ON login
|
||||||
|
EXECUTE FUNCTION init_session();
|
||||||
|
ALTER EVENT TRIGGER init_session ENABLE ALWAYS;
|
||||||
</programlisting>
|
</programlisting>
|
||||||
</para>
|
</para>
|
||||||
</sect1>
|
</sect1>
|
||||||
|
@ -1794,10 +1794,10 @@ ReleaseBulkInsertStatePin(BulkInsertState bistate)
|
|||||||
bistate->current_buf = InvalidBuffer;
|
bistate->current_buf = InvalidBuffer;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Despite the name, we also reset bulk relation extension
|
* Despite the name, we also reset bulk relation extension state.
|
||||||
* state. Otherwise we can end up erroring out due to looking for free
|
* Otherwise we can end up erroring out due to looking for free space in
|
||||||
* space in ->next_free of one partition, even though ->next_free was set
|
* ->next_free of one partition, even though ->next_free was set when
|
||||||
* when extending another partition. It's obviously also could be bad for
|
* extending another partition. It could obviously also be bad for
|
||||||
* efficiency to look at existing blocks at offsets from another
|
* efficiency to look at existing blocks at offsets from another
|
||||||
* partition, even if we don't error out.
|
* partition, even if we don't error out.
|
||||||
*/
|
*/
|
||||||
|
@ -116,7 +116,7 @@ static void movedb(const char *dbname, const char *tblspcname);
|
|||||||
static void movedb_failure_callback(int code, Datum arg);
|
static void movedb_failure_callback(int code, Datum arg);
|
||||||
static bool get_db_info(const char *name, LOCKMODE lockmode,
|
static bool get_db_info(const char *name, LOCKMODE lockmode,
|
||||||
Oid *dbIdP, Oid *ownerIdP,
|
Oid *dbIdP, Oid *ownerIdP,
|
||||||
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
|
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, bool *dbHasLoginEvtP,
|
||||||
TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
|
TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
|
||||||
Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
|
Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
|
||||||
char **dbIcurules,
|
char **dbIcurules,
|
||||||
@ -680,6 +680,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
|
|||||||
char src_locprovider = '\0';
|
char src_locprovider = '\0';
|
||||||
char *src_collversion = NULL;
|
char *src_collversion = NULL;
|
||||||
bool src_istemplate;
|
bool src_istemplate;
|
||||||
|
bool src_hasloginevt;
|
||||||
bool src_allowconn;
|
bool src_allowconn;
|
||||||
TransactionId src_frozenxid = InvalidTransactionId;
|
TransactionId src_frozenxid = InvalidTransactionId;
|
||||||
MultiXactId src_minmxid = InvalidMultiXactId;
|
MultiXactId src_minmxid = InvalidMultiXactId;
|
||||||
@ -968,7 +969,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
|
|||||||
|
|
||||||
if (!get_db_info(dbtemplate, ShareLock,
|
if (!get_db_info(dbtemplate, ShareLock,
|
||||||
&src_dboid, &src_owner, &src_encoding,
|
&src_dboid, &src_owner, &src_encoding,
|
||||||
&src_istemplate, &src_allowconn,
|
&src_istemplate, &src_allowconn, &src_hasloginevt,
|
||||||
&src_frozenxid, &src_minmxid, &src_deftablespace,
|
&src_frozenxid, &src_minmxid, &src_deftablespace,
|
||||||
&src_collate, &src_ctype, &src_iculocale, &src_icurules, &src_locprovider,
|
&src_collate, &src_ctype, &src_iculocale, &src_icurules, &src_locprovider,
|
||||||
&src_collversion))
|
&src_collversion))
|
||||||
@ -1375,6 +1376,7 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
|
|||||||
new_record[Anum_pg_database_datlocprovider - 1] = CharGetDatum(dblocprovider);
|
new_record[Anum_pg_database_datlocprovider - 1] = CharGetDatum(dblocprovider);
|
||||||
new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(dbistemplate);
|
new_record[Anum_pg_database_datistemplate - 1] = BoolGetDatum(dbistemplate);
|
||||||
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(dballowconnections);
|
new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(dballowconnections);
|
||||||
|
new_record[Anum_pg_database_dathasloginevt - 1] = BoolGetDatum(src_hasloginevt);
|
||||||
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
|
new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit);
|
||||||
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
|
new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid);
|
||||||
new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid);
|
new_record[Anum_pg_database_datminmxid - 1] = TransactionIdGetDatum(src_minmxid);
|
||||||
@ -1603,7 +1605,7 @@ dropdb(const char *dbname, bool missing_ok, bool force)
|
|||||||
pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
|
pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
|
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
|
||||||
&db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
|
&db_istemplate, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
{
|
{
|
||||||
if (!missing_ok)
|
if (!missing_ok)
|
||||||
{
|
{
|
||||||
@ -1817,7 +1819,7 @@ RenameDatabase(const char *oldname, const char *newname)
|
|||||||
*/
|
*/
|
||||||
rel = table_open(DatabaseRelationId, RowExclusiveLock);
|
rel = table_open(DatabaseRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL,
|
if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
@ -1927,7 +1929,7 @@ movedb(const char *dbname, const char *tblspcname)
|
|||||||
*/
|
*/
|
||||||
pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
|
pgdbrel = table_open(DatabaseRelationId, RowExclusiveLock);
|
||||||
|
|
||||||
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL,
|
if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL, NULL,
|
||||||
NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL))
|
NULL, NULL, NULL, NULL, &src_tblspcoid, NULL, NULL, NULL, NULL, NULL, NULL))
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
(errcode(ERRCODE_UNDEFINED_DATABASE),
|
||||||
@ -2693,7 +2695,7 @@ pg_database_collation_actual_version(PG_FUNCTION_ARGS)
|
|||||||
static bool
|
static bool
|
||||||
get_db_info(const char *name, LOCKMODE lockmode,
|
get_db_info(const char *name, LOCKMODE lockmode,
|
||||||
Oid *dbIdP, Oid *ownerIdP,
|
Oid *dbIdP, Oid *ownerIdP,
|
||||||
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP,
|
int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, bool *dbHasLoginEvtP,
|
||||||
TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
|
TransactionId *dbFrozenXidP, MultiXactId *dbMinMultiP,
|
||||||
Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
|
Oid *dbTablespace, char **dbCollate, char **dbCtype, char **dbIculocale,
|
||||||
char **dbIcurules,
|
char **dbIcurules,
|
||||||
@ -2778,6 +2780,9 @@ get_db_info(const char *name, LOCKMODE lockmode,
|
|||||||
/* allowed as template? */
|
/* allowed as template? */
|
||||||
if (dbIsTemplateP)
|
if (dbIsTemplateP)
|
||||||
*dbIsTemplateP = dbform->datistemplate;
|
*dbIsTemplateP = dbform->datistemplate;
|
||||||
|
/* Has on login event trigger? */
|
||||||
|
if (dbHasLoginEvtP)
|
||||||
|
*dbHasLoginEvtP = dbform->dathasloginevt;
|
||||||
/* allowing connections? */
|
/* allowing connections? */
|
||||||
if (dbAllowConnP)
|
if (dbAllowConnP)
|
||||||
*dbAllowConnP = dbform->datallowconn;
|
*dbAllowConnP = dbform->datallowconn;
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "catalog/dependency.h"
|
#include "catalog/dependency.h"
|
||||||
#include "catalog/indexing.h"
|
#include "catalog/indexing.h"
|
||||||
#include "catalog/objectaccess.h"
|
#include "catalog/objectaccess.h"
|
||||||
|
#include "catalog/pg_database.h"
|
||||||
#include "catalog/pg_event_trigger.h"
|
#include "catalog/pg_event_trigger.h"
|
||||||
#include "catalog/pg_namespace.h"
|
#include "catalog/pg_namespace.h"
|
||||||
#include "catalog/pg_opclass.h"
|
#include "catalog/pg_opclass.h"
|
||||||
@ -37,15 +38,18 @@
|
|||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
#include "parser/parse_func.h"
|
#include "parser/parse_func.h"
|
||||||
#include "pgstat.h"
|
#include "pgstat.h"
|
||||||
|
#include "storage/lmgr.h"
|
||||||
#include "tcop/deparse_utility.h"
|
#include "tcop/deparse_utility.h"
|
||||||
#include "tcop/utility.h"
|
#include "tcop/utility.h"
|
||||||
#include "utils/acl.h"
|
#include "utils/acl.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/evtcache.h"
|
#include "utils/evtcache.h"
|
||||||
#include "utils/fmgroids.h"
|
#include "utils/fmgroids.h"
|
||||||
|
#include "utils/inval.h"
|
||||||
#include "utils/lsyscache.h"
|
#include "utils/lsyscache.h"
|
||||||
#include "utils/memutils.h"
|
#include "utils/memutils.h"
|
||||||
#include "utils/rel.h"
|
#include "utils/rel.h"
|
||||||
|
#include "utils/snapmgr.h"
|
||||||
#include "utils/syscache.h"
|
#include "utils/syscache.h"
|
||||||
|
|
||||||
typedef struct EventTriggerQueryState
|
typedef struct EventTriggerQueryState
|
||||||
@ -103,6 +107,7 @@ static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
|
|||||||
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
|
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
|
||||||
static const char *stringify_grant_objtype(ObjectType objtype);
|
static const char *stringify_grant_objtype(ObjectType objtype);
|
||||||
static const char *stringify_adefprivs_objtype(ObjectType objtype);
|
static const char *stringify_adefprivs_objtype(ObjectType objtype);
|
||||||
|
static void SetDatatabaseHasLoginEventTriggers(void);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create an event trigger.
|
* Create an event trigger.
|
||||||
@ -133,6 +138,7 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
|
|||||||
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
|
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
|
||||||
strcmp(stmt->eventname, "ddl_command_end") != 0 &&
|
strcmp(stmt->eventname, "ddl_command_end") != 0 &&
|
||||||
strcmp(stmt->eventname, "sql_drop") != 0 &&
|
strcmp(stmt->eventname, "sql_drop") != 0 &&
|
||||||
|
strcmp(stmt->eventname, "login") != 0 &&
|
||||||
strcmp(stmt->eventname, "table_rewrite") != 0)
|
strcmp(stmt->eventname, "table_rewrite") != 0)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
@ -165,6 +171,10 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
|
|||||||
else if (strcmp(stmt->eventname, "table_rewrite") == 0
|
else if (strcmp(stmt->eventname, "table_rewrite") == 0
|
||||||
&& tags != NULL)
|
&& tags != NULL)
|
||||||
validate_table_rewrite_tags("tag", tags);
|
validate_table_rewrite_tags("tag", tags);
|
||||||
|
else if (strcmp(stmt->eventname, "login") == 0 && tags != NULL)
|
||||||
|
ereport(ERROR,
|
||||||
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
|
errmsg("Tag filtering is not supported for login event trigger")));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Give user a nice error message if an event trigger of the same name
|
* Give user a nice error message if an event trigger of the same name
|
||||||
@ -296,6 +306,13 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
|
|||||||
CatalogTupleInsert(tgrel, tuple);
|
CatalogTupleInsert(tgrel, tuple);
|
||||||
heap_freetuple(tuple);
|
heap_freetuple(tuple);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Login event triggers have an additional flag in pg_database to avoid
|
||||||
|
* faster lookups in hot codepaths. Set the flag unless already True.
|
||||||
|
*/
|
||||||
|
if (strcmp(eventname, "login") == 0)
|
||||||
|
SetDatatabaseHasLoginEventTriggers();
|
||||||
|
|
||||||
/* Depend on owner. */
|
/* Depend on owner. */
|
||||||
recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner);
|
recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner);
|
||||||
|
|
||||||
@ -357,6 +374,41 @@ filter_list_to_array(List *filterlist)
|
|||||||
return PointerGetDatum(construct_array_builtin(data, l, TEXTOID));
|
return PointerGetDatum(construct_array_builtin(data, l, TEXTOID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set pg_database.dathasloginevt flag for current database indicating that
|
||||||
|
* current database has on login triggers.
|
||||||
|
*/
|
||||||
|
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 conflit 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.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
|
* ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
|
||||||
*/
|
*/
|
||||||
@ -391,6 +443,14 @@ AlterEventTrigger(AlterEventTrigStmt *stmt)
|
|||||||
|
|
||||||
CatalogTupleUpdate(tgrel, &tup->t_self, tup);
|
CatalogTupleUpdate(tgrel, &tup->t_self, tup);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Login event triggers have an additional flag in pg_database to avoid
|
||||||
|
* faster lookups in hot codepaths. Set the flag unless already True.
|
||||||
|
*/
|
||||||
|
if (namestrcmp(&evtForm->evtevent, "login") == 0 &&
|
||||||
|
tgenabled != TRIGGER_DISABLED)
|
||||||
|
SetDatatabaseHasLoginEventTriggers();
|
||||||
|
|
||||||
InvokeObjectPostAlterHook(EventTriggerRelationId,
|
InvokeObjectPostAlterHook(EventTriggerRelationId,
|
||||||
trigoid, 0);
|
trigoid, 0);
|
||||||
|
|
||||||
@ -549,6 +609,15 @@ filter_event_trigger(CommandTag tag, EventTriggerCacheItem *item)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static CommandTag
|
||||||
|
EventTriggerGetTag(Node *parsetree, EventTriggerEvent event)
|
||||||
|
{
|
||||||
|
if (event == EVT_Login)
|
||||||
|
return CMDTAG_LOGIN;
|
||||||
|
else
|
||||||
|
return CreateCommandTag(parsetree);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Setup for running triggers for the given event. Return value is an OID list
|
* Setup for running triggers for the given event. Return value is an OID list
|
||||||
* of functions to run; if there are any, trigdata is filled with an
|
* of functions to run; if there are any, trigdata is filled with an
|
||||||
@ -557,7 +626,7 @@ filter_event_trigger(CommandTag tag, EventTriggerCacheItem *item)
|
|||||||
static List *
|
static List *
|
||||||
EventTriggerCommonSetup(Node *parsetree,
|
EventTriggerCommonSetup(Node *parsetree,
|
||||||
EventTriggerEvent event, const char *eventstr,
|
EventTriggerEvent event, const char *eventstr,
|
||||||
EventTriggerData *trigdata)
|
EventTriggerData *trigdata, bool unfiltered)
|
||||||
{
|
{
|
||||||
CommandTag tag;
|
CommandTag tag;
|
||||||
List *cachelist;
|
List *cachelist;
|
||||||
@ -582,10 +651,12 @@ EventTriggerCommonSetup(Node *parsetree,
|
|||||||
{
|
{
|
||||||
CommandTag dbgtag;
|
CommandTag dbgtag;
|
||||||
|
|
||||||
dbgtag = CreateCommandTag(parsetree);
|
dbgtag = EventTriggerGetTag(parsetree, event);
|
||||||
|
|
||||||
if (event == EVT_DDLCommandStart ||
|
if (event == EVT_DDLCommandStart ||
|
||||||
event == EVT_DDLCommandEnd ||
|
event == EVT_DDLCommandEnd ||
|
||||||
event == EVT_SQLDrop)
|
event == EVT_SQLDrop ||
|
||||||
|
event == EVT_Login)
|
||||||
{
|
{
|
||||||
if (!command_tag_event_trigger_ok(dbgtag))
|
if (!command_tag_event_trigger_ok(dbgtag))
|
||||||
elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
|
elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
|
||||||
@ -604,7 +675,7 @@ EventTriggerCommonSetup(Node *parsetree,
|
|||||||
return NIL;
|
return NIL;
|
||||||
|
|
||||||
/* Get the command tag. */
|
/* Get the command tag. */
|
||||||
tag = CreateCommandTag(parsetree);
|
tag = EventTriggerGetTag(parsetree, event);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Filter list of event triggers by command tag, and copy them into our
|
* Filter list of event triggers by command tag, and copy them into our
|
||||||
@ -617,7 +688,7 @@ EventTriggerCommonSetup(Node *parsetree,
|
|||||||
{
|
{
|
||||||
EventTriggerCacheItem *item = lfirst(lc);
|
EventTriggerCacheItem *item = lfirst(lc);
|
||||||
|
|
||||||
if (filter_event_trigger(tag, item))
|
if (unfiltered || filter_event_trigger(tag, item))
|
||||||
{
|
{
|
||||||
/* We must plan to fire this trigger. */
|
/* We must plan to fire this trigger. */
|
||||||
runlist = lappend_oid(runlist, item->fnoid);
|
runlist = lappend_oid(runlist, item->fnoid);
|
||||||
@ -670,7 +741,7 @@ EventTriggerDDLCommandStart(Node *parsetree)
|
|||||||
runlist = EventTriggerCommonSetup(parsetree,
|
runlist = EventTriggerCommonSetup(parsetree,
|
||||||
EVT_DDLCommandStart,
|
EVT_DDLCommandStart,
|
||||||
"ddl_command_start",
|
"ddl_command_start",
|
||||||
&trigdata);
|
&trigdata, false);
|
||||||
if (runlist == NIL)
|
if (runlist == NIL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -718,7 +789,7 @@ EventTriggerDDLCommandEnd(Node *parsetree)
|
|||||||
|
|
||||||
runlist = EventTriggerCommonSetup(parsetree,
|
runlist = EventTriggerCommonSetup(parsetree,
|
||||||
EVT_DDLCommandEnd, "ddl_command_end",
|
EVT_DDLCommandEnd, "ddl_command_end",
|
||||||
&trigdata);
|
&trigdata, false);
|
||||||
if (runlist == NIL)
|
if (runlist == NIL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -764,7 +835,7 @@ EventTriggerSQLDrop(Node *parsetree)
|
|||||||
|
|
||||||
runlist = EventTriggerCommonSetup(parsetree,
|
runlist = EventTriggerCommonSetup(parsetree,
|
||||||
EVT_SQLDrop, "sql_drop",
|
EVT_SQLDrop, "sql_drop",
|
||||||
&trigdata);
|
&trigdata, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Nothing to do if run list is empty. Note this typically can't happen,
|
* Nothing to do if run list is empty. Note this typically can't happen,
|
||||||
@ -805,6 +876,97 @@ EventTriggerSQLDrop(Node *parsetree)
|
|||||||
list_free(runlist);
|
list_free(runlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 or via a GUC. We also need a
|
||||||
|
* database connection (some background workers doesn't have it).
|
||||||
|
*/
|
||||||
|
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 was 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.
|
||||||
|
*/
|
||||||
|
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 login
|
||||||
|
* trigger, it will update pg_database.dathasloginevt *afterwards*.
|
||||||
|
*/
|
||||||
|
runlist = EventTriggerCommonSetup(NULL,
|
||||||
|
EVT_Login, "login",
|
||||||
|
&trigdata, true);
|
||||||
|
|
||||||
|
if (runlist == NIL)
|
||||||
|
{
|
||||||
|
Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
|
||||||
|
HeapTuple tuple;
|
||||||
|
Form_pg_database db;
|
||||||
|
|
||||||
|
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 = false;
|
||||||
|
CatalogTupleUpdate(pg_db, &tuple->t_self, tuple);
|
||||||
|
}
|
||||||
|
table_close(pg_db, RowExclusiveLock);
|
||||||
|
heap_freetuple(tuple);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
list_free(runlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommitTransactionCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fire table_rewrite triggers.
|
* Fire table_rewrite triggers.
|
||||||
@ -835,7 +997,7 @@ EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
|
|||||||
runlist = EventTriggerCommonSetup(parsetree,
|
runlist = EventTriggerCommonSetup(parsetree,
|
||||||
EVT_TableRewrite,
|
EVT_TableRewrite,
|
||||||
"table_rewrite",
|
"table_rewrite",
|
||||||
&trigdata);
|
&trigdata, false);
|
||||||
if (runlist == NIL)
|
if (runlist == NIL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1060,6 +1060,44 @@ LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
|||||||
AcceptInvalidationMessages();
|
AcceptInvalidationMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ConditionalLockSharedObject
|
||||||
|
*
|
||||||
|
* As above, but only lock if we can get the lock without blocking.
|
||||||
|
* Returns true iff the lock was acquired.
|
||||||
|
*/
|
||||||
|
bool
|
||||||
|
ConditionalLockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
||||||
|
LOCKMODE lockmode)
|
||||||
|
{
|
||||||
|
LOCKTAG tag;
|
||||||
|
LOCALLOCK *locallock;
|
||||||
|
LockAcquireResult res;
|
||||||
|
|
||||||
|
SET_LOCKTAG_OBJECT(tag,
|
||||||
|
InvalidOid,
|
||||||
|
classid,
|
||||||
|
objid,
|
||||||
|
objsubid);
|
||||||
|
|
||||||
|
res = LockAcquireExtended(&tag, lockmode, false, true, true, &locallock);
|
||||||
|
|
||||||
|
if (res == LOCKACQUIRE_NOT_AVAIL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that we have the lock, check for invalidation messages; see notes
|
||||||
|
* in LockRelationOid.
|
||||||
|
*/
|
||||||
|
if (res != LOCKACQUIRE_ALREADY_CLEAR)
|
||||||
|
{
|
||||||
|
AcceptInvalidationMessages();
|
||||||
|
MarkLockClear(locallock);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* UnlockSharedObject
|
* UnlockSharedObject
|
||||||
*/
|
*/
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
#include "access/xact.h"
|
#include "access/xact.h"
|
||||||
#include "catalog/pg_type.h"
|
#include "catalog/pg_type.h"
|
||||||
#include "commands/async.h"
|
#include "commands/async.h"
|
||||||
|
#include "commands/event_trigger.h"
|
||||||
#include "commands/prepare.h"
|
#include "commands/prepare.h"
|
||||||
#include "common/pg_prng.h"
|
#include "common/pg_prng.h"
|
||||||
#include "jit/jit.h"
|
#include "jit/jit.h"
|
||||||
@ -4289,6 +4290,9 @@ PostgresMain(const char *dbname, const char *username)
|
|||||||
initStringInfo(&row_description_buf);
|
initStringInfo(&row_description_buf);
|
||||||
MemoryContextSwitchTo(TopMemoryContext);
|
MemoryContextSwitchTo(TopMemoryContext);
|
||||||
|
|
||||||
|
/* Fire any defined login event triggers, if appropriate */
|
||||||
|
EventTriggerOnLogin();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* POSTGRES main processing loop begins here
|
* POSTGRES main processing loop begins here
|
||||||
*
|
*
|
||||||
|
2
src/backend/utils/cache/evtcache.c
vendored
2
src/backend/utils/cache/evtcache.c
vendored
@ -167,6 +167,8 @@ BuildEventTriggerCache(void)
|
|||||||
event = EVT_SQLDrop;
|
event = EVT_SQLDrop;
|
||||||
else if (strcmp(evtevent, "table_rewrite") == 0)
|
else if (strcmp(evtevent, "table_rewrite") == 0)
|
||||||
event = EVT_TableRewrite;
|
event = EVT_TableRewrite;
|
||||||
|
else if (strcmp(evtevent, "login") == 0)
|
||||||
|
event = EVT_Login;
|
||||||
else
|
else
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -90,6 +90,8 @@ Oid MyDatabaseId = InvalidOid;
|
|||||||
|
|
||||||
Oid MyDatabaseTableSpace = InvalidOid;
|
Oid MyDatabaseTableSpace = InvalidOid;
|
||||||
|
|
||||||
|
bool MyDatabaseHasLoginEventTriggers = false;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* DatabasePath is the path (relative to DataDir) of my database's
|
* DatabasePath is the path (relative to DataDir) of my database's
|
||||||
* primary directory, ie, its directory in the default tablespace.
|
* primary directory, ie, its directory in the default tablespace.
|
||||||
|
@ -1103,6 +1103,7 @@ InitPostgres(const char *in_dbname, Oid dboid,
|
|||||||
}
|
}
|
||||||
|
|
||||||
MyDatabaseTableSpace = datform->dattablespace;
|
MyDatabaseTableSpace = datform->dattablespace;
|
||||||
|
MyDatabaseHasLoginEventTriggers = datform->dathasloginevt;
|
||||||
/* pass the database name back to the caller */
|
/* pass the database name back to the caller */
|
||||||
if (out_dbname)
|
if (out_dbname)
|
||||||
strcpy(out_dbname, dbname);
|
strcpy(out_dbname, dbname);
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
#include "common/controldata_utils.h"
|
#include "common/controldata_utils.h"
|
||||||
#include "funcapi.h"
|
#include "funcapi.h"
|
||||||
#include "miscadmin.h"
|
#include "miscadmin.h"
|
||||||
|
#include "storage/lwlock.h"
|
||||||
#include "utils/builtins.h"
|
#include "utils/builtins.h"
|
||||||
#include "utils/pg_lsn.h"
|
#include "utils/pg_lsn.h"
|
||||||
#include "utils/timestamp.h"
|
#include "utils/timestamp.h"
|
||||||
@ -42,7 +43,9 @@ pg_control_system(PG_FUNCTION_ARGS)
|
|||||||
elog(ERROR, "return type must be a row type");
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
/* read the control file */
|
/* read the control file */
|
||||||
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
||||||
ControlFile = get_controlfile(DataDir, &crc_ok);
|
ControlFile = get_controlfile(DataDir, &crc_ok);
|
||||||
|
LWLockRelease(ControlFileLock);
|
||||||
if (!crc_ok)
|
if (!crc_ok)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errmsg("calculated CRC checksum does not match value stored in file")));
|
(errmsg("calculated CRC checksum does not match value stored in file")));
|
||||||
@ -80,7 +83,9 @@ pg_control_checkpoint(PG_FUNCTION_ARGS)
|
|||||||
elog(ERROR, "return type must be a row type");
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
/* Read the control file. */
|
/* Read the control file. */
|
||||||
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
||||||
ControlFile = get_controlfile(DataDir, &crc_ok);
|
ControlFile = get_controlfile(DataDir, &crc_ok);
|
||||||
|
LWLockRelease(ControlFileLock);
|
||||||
if (!crc_ok)
|
if (!crc_ok)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errmsg("calculated CRC checksum does not match value stored in file")));
|
(errmsg("calculated CRC checksum does not match value stored in file")));
|
||||||
@ -169,7 +174,9 @@ pg_control_recovery(PG_FUNCTION_ARGS)
|
|||||||
elog(ERROR, "return type must be a row type");
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
/* read the control file */
|
/* read the control file */
|
||||||
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
||||||
ControlFile = get_controlfile(DataDir, &crc_ok);
|
ControlFile = get_controlfile(DataDir, &crc_ok);
|
||||||
|
LWLockRelease(ControlFileLock);
|
||||||
if (!crc_ok)
|
if (!crc_ok)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errmsg("calculated CRC checksum does not match value stored in file")));
|
(errmsg("calculated CRC checksum does not match value stored in file")));
|
||||||
@ -208,7 +215,9 @@ pg_control_init(PG_FUNCTION_ARGS)
|
|||||||
elog(ERROR, "return type must be a row type");
|
elog(ERROR, "return type must be a row type");
|
||||||
|
|
||||||
/* read the control file */
|
/* read the control file */
|
||||||
|
LWLockAcquire(ControlFileLock, LW_SHARED);
|
||||||
ControlFile = get_controlfile(DataDir, &crc_ok);
|
ControlFile = get_controlfile(DataDir, &crc_ok);
|
||||||
|
LWLockRelease(ControlFileLock);
|
||||||
if (!crc_ok)
|
if (!crc_ok)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errmsg("calculated CRC checksum does not match value stored in file")));
|
(errmsg("calculated CRC checksum does not match value stored in file")));
|
||||||
|
@ -3263,6 +3263,11 @@ dumpDatabase(Archive *fout)
|
|||||||
appendPQExpBufferStr(delQry, ";\n");
|
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 */
|
/* Add database-specific SET options */
|
||||||
dumpDatabaseConfig(fout, creaQry, datname, dbCatId.oid);
|
dumpDatabaseConfig(fout, creaQry, datname, dbCatId.oid);
|
||||||
|
|
||||||
|
@ -3552,8 +3552,8 @@ psql_completion(const char *text, int start, int end)
|
|||||||
COMPLETE_WITH("ON");
|
COMPLETE_WITH("ON");
|
||||||
/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
|
/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
|
||||||
else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
|
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",
|
||||||
"table_rewrite");
|
"sql_drop", "table_rewrite");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Complete CREATE EVENT TRIGGER <name> ON <event_type>. EXECUTE FUNCTION
|
* Complete CREATE EVENT TRIGGER <name> ON <event_type>. EXECUTE FUNCTION
|
||||||
|
@ -57,6 +57,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* yyyymmddN */
|
/* yyyymmddN */
|
||||||
#define CATALOG_VERSION_NO 202310141
|
#define CATALOG_VERSION_NO 202310161
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
descr => 'default template for new databases',
|
descr => 'default template for new databases',
|
||||||
datname => 'template1', encoding => 'ENCODING',
|
datname => 'template1', encoding => 'ENCODING',
|
||||||
datlocprovider => 'LOCALE_PROVIDER', datistemplate => 't',
|
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',
|
datminmxid => '1', dattablespace => 'pg_default', datcollate => 'LC_COLLATE',
|
||||||
datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE',
|
datctype => 'LC_CTYPE', daticulocale => 'ICU_LOCALE',
|
||||||
daticurules => 'ICU_RULES', datacl => '_null_' },
|
daticurules => 'ICU_RULES', datacl => '_null_' },
|
||||||
|
@ -49,6 +49,9 @@ CATALOG(pg_database,1262,DatabaseRelationId) BKI_SHARED_RELATION BKI_ROWTYPE_OID
|
|||||||
/* new connections allowed? */
|
/* new connections allowed? */
|
||||||
bool datallowconn;
|
bool datallowconn;
|
||||||
|
|
||||||
|
/* database has login event triggers? */
|
||||||
|
bool dathasloginevt;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Max connections allowed. Negative values have special meaning, see
|
* Max connections allowed. Negative values have special meaning, see
|
||||||
* DATCONNLIMIT_* defines below.
|
* DATCONNLIMIT_* defines below.
|
||||||
|
@ -56,6 +56,7 @@ extern void EventTriggerDDLCommandStart(Node *parsetree);
|
|||||||
extern void EventTriggerDDLCommandEnd(Node *parsetree);
|
extern void EventTriggerDDLCommandEnd(Node *parsetree);
|
||||||
extern void EventTriggerSQLDrop(Node *parsetree);
|
extern void EventTriggerSQLDrop(Node *parsetree);
|
||||||
extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason);
|
extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason);
|
||||||
|
extern void EventTriggerOnLogin(void);
|
||||||
|
|
||||||
extern bool EventTriggerBeginCompleteQuery(void);
|
extern bool EventTriggerBeginCompleteQuery(void);
|
||||||
extern void EventTriggerEndCompleteQuery(void);
|
extern void EventTriggerEndCompleteQuery(void);
|
||||||
|
@ -203,6 +203,8 @@ extern PGDLLIMPORT Oid MyDatabaseId;
|
|||||||
|
|
||||||
extern PGDLLIMPORT Oid MyDatabaseTableSpace;
|
extern PGDLLIMPORT Oid MyDatabaseTableSpace;
|
||||||
|
|
||||||
|
extern PGDLLIMPORT bool MyDatabaseHasLoginEventTriggers;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Date/Time Configuration
|
* Date/Time Configuration
|
||||||
*
|
*
|
||||||
|
@ -99,6 +99,8 @@ extern void UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
|
|||||||
/* Lock a shared-across-databases object (other than a relation) */
|
/* Lock a shared-across-databases object (other than a relation) */
|
||||||
extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
||||||
LOCKMODE lockmode);
|
LOCKMODE lockmode);
|
||||||
|
extern bool ConditionalLockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
||||||
|
LOCKMODE lockmode);
|
||||||
extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
|
||||||
LOCKMODE lockmode);
|
LOCKMODE lockmode);
|
||||||
|
|
||||||
|
@ -186,6 +186,7 @@ PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
|
|||||||
PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
|
PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
|
||||||
PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
|
PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
|
||||||
PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
|
PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
|
||||||
|
PG_CMDTAG(CMDTAG_LOGIN, "LOGIN", true, false, false)
|
||||||
PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true)
|
PG_CMDTAG(CMDTAG_MERGE, "MERGE", false, false, true)
|
||||||
PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
|
PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
|
||||||
PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
|
PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
|
||||||
|
@ -22,7 +22,8 @@ typedef enum
|
|||||||
EVT_DDLCommandStart,
|
EVT_DDLCommandStart,
|
||||||
EVT_DDLCommandEnd,
|
EVT_DDLCommandEnd,
|
||||||
EVT_SQLDrop,
|
EVT_SQLDrop,
|
||||||
EVT_TableRewrite
|
EVT_TableRewrite,
|
||||||
|
EVT_Login,
|
||||||
} EventTriggerEvent;
|
} EventTriggerEvent;
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
|
189
src/test/authentication/t/006_login_trigger.pl
Normal file
189
src/test/authentication/t/006_login_trigger.pl
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
# Copyright (c) 2021-2023, PostgreSQL Global Development Group
|
||||||
|
|
||||||
|
# Tests of authentication via login trigger. Mostly for rejection via
|
||||||
|
# exception, because this scenario cannot be covered with *.sql/*.out regress
|
||||||
|
# tests.
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use PostgreSQL::Test::Cluster;
|
||||||
|
use PostgreSQL::Test::Utils;
|
||||||
|
use Test::More;
|
||||||
|
|
||||||
|
# Execute a psql command and compare its output towards given regexps
|
||||||
|
sub psql_command
|
||||||
|
{
|
||||||
|
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||||
|
my ($node, $sql, $expected_ret, $test_name, %params) = @_;
|
||||||
|
|
||||||
|
my $connstr;
|
||||||
|
if (defined($params{connstr}))
|
||||||
|
{
|
||||||
|
$connstr = $params{connstr};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$connstr = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
# Execute command
|
||||||
|
my ($ret, $stdout, $stderr) =
|
||||||
|
$node->psql('postgres', $sql, connstr => "$connstr");
|
||||||
|
|
||||||
|
# Check return code
|
||||||
|
is($ret, $expected_ret, "$test_name: exit code $expected_ret");
|
||||||
|
|
||||||
|
# Check stdout
|
||||||
|
if (defined($params{log_like}))
|
||||||
|
{
|
||||||
|
my @log_like = @{ $params{log_like} };
|
||||||
|
while (my $regex = shift @log_like)
|
||||||
|
{
|
||||||
|
like($stdout, $regex, "$test_name: log matches");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined($params{log_unlike}))
|
||||||
|
{
|
||||||
|
my @log_unlike = @{ $params{log_unlike} };
|
||||||
|
while (my $regex = shift @log_unlike)
|
||||||
|
{
|
||||||
|
unlike($stdout, $regex, "$test_name: log unmatches");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined($params{log_exact}))
|
||||||
|
{
|
||||||
|
is($stdout, $params{log_exact}, "$test_name: log equals");
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check stderr
|
||||||
|
if (defined($params{err_like}))
|
||||||
|
{
|
||||||
|
my @err_like = @{ $params{err_like} };
|
||||||
|
while (my $regex = shift @err_like)
|
||||||
|
{
|
||||||
|
like($stderr, $regex, "$test_name: err matches");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined($params{err_unlike}))
|
||||||
|
{
|
||||||
|
my @err_unlike = @{ $params{err_unlike} };
|
||||||
|
while (my $regex = shift @err_unlike)
|
||||||
|
{
|
||||||
|
unlike($stderr, $regex, "$test_name: err unmatches");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (defined($params{err_exact}))
|
||||||
|
{
|
||||||
|
is($stderr, $params{err_exact}, "$test_name: err equals");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# New node
|
||||||
|
my $node = PostgreSQL::Test::Cluster->new('main');
|
||||||
|
$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]);
|
||||||
|
$node->append_conf(
|
||||||
|
'postgresql.conf', q{
|
||||||
|
wal_level = 'logical'
|
||||||
|
max_replication_slots = 4
|
||||||
|
max_wal_senders = 4
|
||||||
|
});
|
||||||
|
$node->start;
|
||||||
|
|
||||||
|
# Create temporary roles and log table
|
||||||
|
psql_command(
|
||||||
|
$node, 'CREATE ROLE regress_alice WITH LOGIN;
|
||||||
|
CREATE ROLE regress_mallory WITH LOGIN;
|
||||||
|
CREATE TABLE user_logins(id serial, who text);
|
||||||
|
GRANT SELECT ON user_logins TO public;
|
||||||
|
', 0, 'create tmp objects',
|
||||||
|
log_exact => '',
|
||||||
|
err_exact => ''),
|
||||||
|
;
|
||||||
|
|
||||||
|
# Create login event function and trigger
|
||||||
|
psql_command(
|
||||||
|
$node,
|
||||||
|
'CREATE FUNCTION on_login_proc() RETURNS event_trigger AS $$
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO user_logins (who) VALUES (SESSION_USER);
|
||||||
|
IF SESSION_USER = \'regress_mallory\' THEN
|
||||||
|
RAISE EXCEPTION \'Hello %! You are NOT welcome here!\', SESSION_USER;
|
||||||
|
END IF;
|
||||||
|
RAISE NOTICE \'Hello %! You are welcome!\', SESSION_USER;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||||
|
', 0, 'create trigger function',
|
||||||
|
log_exact => '',
|
||||||
|
err_exact => '');
|
||||||
|
|
||||||
|
psql_command(
|
||||||
|
$node,
|
||||||
|
'CREATE EVENT TRIGGER on_login_trigger '
|
||||||
|
. 'ON login EXECUTE PROCEDURE on_login_proc();', 0,
|
||||||
|
'create event trigger',
|
||||||
|
log_exact => '',
|
||||||
|
err_exact => '');
|
||||||
|
psql_command(
|
||||||
|
$node, 'ALTER EVENT TRIGGER on_login_trigger ENABLE ALWAYS;', 0,
|
||||||
|
'alter event trigger',
|
||||||
|
log_exact => '',
|
||||||
|
err_like => [qr/You are welcome/]);
|
||||||
|
|
||||||
|
# Check the two requests were logged via login trigger
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT COUNT(*) FROM user_logins;', 0, 'select count',
|
||||||
|
log_exact => '2',
|
||||||
|
err_like => [qr/You are welcome/]);
|
||||||
|
|
||||||
|
# Try to log as allowed Alice and disallowed Mallory (two times)
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT 1;', 0, 'try regress_alice',
|
||||||
|
connstr => 'user=regress_alice',
|
||||||
|
log_exact => '1',
|
||||||
|
err_like => [qr/You are welcome/],
|
||||||
|
err_unlike => [qr/You are NOT welcome/]);
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT 1;', 2, 'try regress_mallory',
|
||||||
|
connstr => 'user=regress_mallory',
|
||||||
|
log_exact => '',
|
||||||
|
err_like => [qr/You are NOT welcome/],
|
||||||
|
err_unlike => [qr/You are welcome/]);
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT 1;', 2, 'try regress_mallory',
|
||||||
|
connstr => 'user=regress_mallory',
|
||||||
|
log_exact => '',
|
||||||
|
err_like => [qr/You are NOT welcome/],
|
||||||
|
err_unlike => [qr/You are welcome/]);
|
||||||
|
|
||||||
|
# Check that Alice's login record is here, while the Mallory's one is not
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT * FROM user_logins;', 0, 'select *',
|
||||||
|
log_like => [qr/3\|regress_alice/],
|
||||||
|
log_unlike => [qr/regress_mallory/],
|
||||||
|
err_like => [qr/You are welcome/]);
|
||||||
|
|
||||||
|
# Check total number of successful logins so far
|
||||||
|
psql_command(
|
||||||
|
$node, 'SELECT COUNT(*) FROM user_logins;', 0, 'select count',
|
||||||
|
log_exact => '5',
|
||||||
|
err_like => [qr/You are welcome/]);
|
||||||
|
|
||||||
|
# Cleanup the temporary stuff
|
||||||
|
psql_command(
|
||||||
|
$node, 'DROP EVENT TRIGGER on_login_trigger;', 0,
|
||||||
|
'drop event trigger',
|
||||||
|
log_exact => '',
|
||||||
|
err_like => [qr/You are welcome/]);
|
||||||
|
psql_command(
|
||||||
|
$node, 'DROP TABLE user_logins;
|
||||||
|
DROP FUNCTION on_login_proc;
|
||||||
|
DROP ROLE regress_mallory;
|
||||||
|
DROP ROLE regress_alice;
|
||||||
|
', 0, 'cleanup',
|
||||||
|
log_exact => '',
|
||||||
|
err_exact => '');
|
||||||
|
|
||||||
|
done_testing();
|
@ -46,6 +46,25 @@ $node_standby_2->start;
|
|||||||
$node_primary->safe_psql('postgres',
|
$node_primary->safe_psql('postgres',
|
||||||
"CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a");
|
"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
|
# Wait for standbys to catch up
|
||||||
$node_primary->wait_for_replay_catchup($node_standby_1);
|
$node_primary->wait_for_replay_catchup($node_standby_1);
|
||||||
$node_standby_1->wait_for_replay_catchup($node_standby_2, $node_primary);
|
$node_standby_1->wait_for_replay_catchup($node_standby_2, $node_primary);
|
||||||
@ -384,6 +403,13 @@ sub replay_check
|
|||||||
|
|
||||||
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";
|
note "enabling hot_standby_feedback";
|
||||||
|
|
||||||
# Enable hs_feedback. The slot should gain an xmin. We set the status interval
|
# Enable hs_feedback. The slot should gain an xmin. We set the status interval
|
||||||
|
@ -638,3 +638,48 @@ NOTICE: DROP POLICY dropped policy
|
|||||||
CREATE POLICY pguc ON event_trigger_test USING (FALSE);
|
CREATE POLICY pguc ON event_trigger_test USING (FALSE);
|
||||||
SET event_triggers = 'off';
|
SET event_triggers = 'off';
|
||||||
DROP POLICY pguc ON event_trigger_test;
|
DROP POLICY pguc ON event_trigger_test;
|
||||||
|
-- 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();
|
||||||
|
\c
|
||||||
|
SELECT dathasloginevt FROM pg_database WHERE datname= :'DBNAME';
|
||||||
|
dathasloginevt
|
||||||
|
----------------
|
||||||
|
f
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
@ -495,3 +495,29 @@ DROP POLICY pguc ON event_trigger_test;
|
|||||||
CREATE POLICY pguc ON event_trigger_test USING (FALSE);
|
CREATE POLICY pguc ON event_trigger_test USING (FALSE);
|
||||||
SET event_triggers = 'off';
|
SET event_triggers = 'off';
|
||||||
DROP POLICY pguc ON event_trigger_test;
|
DROP POLICY pguc ON event_trigger_test;
|
||||||
|
|
||||||
|
-- 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();
|
||||||
|
\c
|
||||||
|
SELECT dathasloginevt FROM pg_database WHERE datname= :'DBNAME';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user