diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 48631cc341..219ba9abf3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -12794,6 +12794,13 @@ postgres=# SELECT * FROM unnest2(ARRAY[[1,2],[3,4]]); server start time + + pg_trigger_depth() + int + current nesting level of PostgreSQL triggers + (0 if not called, directly or indirectly, from inside a trigger) + + session_user name diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index f5e12e5681..2838b66e40 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -59,6 +59,8 @@ /* GUC variables */ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN; +/* How many levels deep into trigger execution are we? */ +static int MyTriggerDepth = 0; #define GetModifiedColumns(relinfo, estate) \ (rt_fetch((relinfo)->ri_RangeTableIndex, (estate)->es_range_table)->modifiedCols) @@ -1838,7 +1840,18 @@ ExecCallTriggerFunc(TriggerData *trigdata, pgstat_init_function_usage(&fcinfo, &fcusage); - result = FunctionCallInvoke(&fcinfo); + MyTriggerDepth++; + PG_TRY(); + { + result = FunctionCallInvoke(&fcinfo); + } + PG_CATCH(); + { + MyTriggerDepth--; + PG_RE_THROW(); + } + PG_END_TRY(); + MyTriggerDepth--; pgstat_end_function_usage(&fcusage, true); @@ -4632,3 +4645,9 @@ AfterTriggerSaveEvent(EState *estate, ResultRelInfo *relinfo, &new_event, &new_shared); } } + +Datum +pg_trigger_depth(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT32(MyTriggerDepth); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index b6ac1959cc..ef5ca3047d 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -2698,6 +2698,9 @@ DESCR("statistics: reset collected statistics for a single table or index in the DATA(insert OID = 3777 ( pg_stat_reset_single_function_counters PGNSP PGUID 12 1 0 0 0 f f f f f v 1 0 2278 "26" _null_ _null_ _null_ _null_ pg_stat_reset_single_function_counters _null_ _null_ _null_ )); DESCR("statistics: reset collected statistics for a single function in the current database"); +DATA(insert OID = 3163 ( pg_trigger_depth PGNSP PGUID 12 1 0 0 0 f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ )); +DESCR("current trigger depth"); + DATA(insert OID = 3778 ( pg_tablespace_location PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 25 "26" _null_ _null_ _null_ _null_ pg_tablespace_location _null_ _null_ _null_ )); DESCR("tablespace location"); diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index ba0f32464e..93033414d9 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -205,4 +205,6 @@ extern bool RI_Initial_Check(Trigger *trigger, extern int RI_FKey_trigger_type(Oid tgfoid); +extern Datum pg_trigger_depth(PG_FUNCTION_ARGS); + #endif /* TRIGGER_H */ diff --git a/src/test/regress/expected/triggers.out b/src/test/regress/expected/triggers.out index b4d391974d..d039b6e38b 100644 --- a/src/test/regress/expected/triggers.out +++ b/src/test/regress/expected/triggers.out @@ -1443,3 +1443,120 @@ NOTICE: drop cascades to 2 other objects DETAIL: drop cascades to view city_view drop cascades to view european_city_view DROP TABLE country_table; +-- Test pg_trigger_depth() +create table depth_a (id int not null primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "depth_a_pkey" for table "depth_a" +create table depth_b (id int not null primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "depth_b_pkey" for table "depth_b" +create table depth_c (id int not null primary key); +NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "depth_c_pkey" for table "depth_c" +create function depth_a_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + insert into depth_b values (new.id); + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_a_tr before insert on depth_a + for each row execute procedure depth_a_tf(); +create function depth_b_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + begin + execute 'insert into depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: depth = %', pg_trigger_depth(); + end; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + execute 'insert into depth_c values (' || new.id::text || ')'; + end if; + return new; +end; +$$; +create trigger depth_b_tr before insert on depth_b + for each row execute procedure depth_b_tf(); +create function depth_c_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + raise exception sqlstate 'U9999'; + end if; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_c_tr before insert on depth_c + for each row execute procedure depth_c_tf(); +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +insert into depth_a values (1); +NOTICE: depth_a_tr: depth = 1 +NOTICE: depth_b_tr: depth = 2 +CONTEXT: SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_c_tr: depth = 3 +CONTEXT: SQL statement "insert into depth_c values (1)" +PL/pgSQL function "depth_b_tf" line 5 at EXECUTE statement +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: SQLSTATE = U9999: depth = 2 +CONTEXT: SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_b_tr: depth = 2 +CONTEXT: SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_c_tr: depth = 3 +CONTEXT: SQL statement "insert into depth_c values (1)" +PL/pgSQL function "depth_b_tf" line 12 at EXECUTE statement +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +ERROR: U9999 +CONTEXT: SQL statement "insert into depth_c values (1)" +PL/pgSQL function "depth_b_tf" line 12 at EXECUTE statement +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +insert into depth_a values (2); +NOTICE: depth_a_tr: depth = 1 +NOTICE: depth_b_tr: depth = 2 +CONTEXT: SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_c_tr: depth = 3 +CONTEXT: SQL statement "insert into depth_c values (2)" +PL/pgSQL function "depth_b_tf" line 5 at EXECUTE statement +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_c_tr: depth = 3 +CONTEXT: SQL statement "insert into depth_c values (2)" +PL/pgSQL function "depth_b_tf" line 5 at EXECUTE statement +SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_b_tr: depth = 2 +CONTEXT: SQL statement "insert into depth_b values (new.id)" +PL/pgSQL function "depth_a_tf" line 4 at SQL statement +NOTICE: depth_a_tr: depth = 1 +select pg_trigger_depth(); + pg_trigger_depth +------------------ + 0 +(1 row) + +drop table depth_a, depth_b, depth_c; +drop function depth_a_tf(); +drop function depth_b_tf(); +drop function depth_c_tf(); diff --git a/src/test/regress/sql/triggers.sql b/src/test/regress/sql/triggers.sql index e52131dbba..f88cb81940 100644 --- a/src/test/regress/sql/triggers.sql +++ b/src/test/regress/sql/triggers.sql @@ -961,3 +961,67 @@ SELECT * FROM city_view; DROP TABLE city_table CASCADE; DROP TABLE country_table; + + +-- Test pg_trigger_depth() + +create table depth_a (id int not null primary key); +create table depth_b (id int not null primary key); +create table depth_c (id int not null primary key); + +create function depth_a_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + insert into depth_b values (new.id); + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_a_tr before insert on depth_a + for each row execute procedure depth_a_tf(); + +create function depth_b_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + begin + execute 'insert into depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: depth = %', pg_trigger_depth(); + end; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + execute 'insert into depth_c values (' || new.id::text || ')'; + end if; + return new; +end; +$$; +create trigger depth_b_tr before insert on depth_b + for each row execute procedure depth_b_tf(); + +create function depth_c_tf() returns trigger + language plpgsql as $$ +begin + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + if new.id = 1 then + raise exception sqlstate 'U9999'; + end if; + raise notice '%: depth = %', tg_name, pg_trigger_depth(); + return new; +end; +$$; +create trigger depth_c_tr before insert on depth_c + for each row execute procedure depth_c_tf(); + +select pg_trigger_depth(); +insert into depth_a values (1); +select pg_trigger_depth(); +insert into depth_a values (2); +select pg_trigger_depth(); + +drop table depth_a, depth_b, depth_c; +drop function depth_a_tf(); +drop function depth_b_tf(); +drop function depth_c_tf();