*** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** *** 3207,3212 **** RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id; --- 3207,3222 ---- + TG_DEPTH + + + Data type integer; the current number of levels of + nesting within trigger execution. + + + + + TG_NARGS *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** *** 142,147 **** typedef struct TransactionStateData --- 142,148 ---- Oid prevUser; /* previous CurrentUserId setting */ int prevSecContext; /* previous SecurityRestrictionContext */ bool prevXactReadOnly; /* entry-time xact r/o state */ + int prevTgDepth; /* previous trigger depth */ bool startedInRecovery; /* did we start in recovery? */ struct TransactionStateData *parent; /* back link to parent */ } TransactionStateData; *************** *** 171,176 **** static TransactionStateData TopTransactionStateData = { --- 172,178 ---- InvalidOid, /* previous CurrentUserId setting */ 0, /* previous SecurityRestrictionContext */ false, /* entry-time xact r/o state */ + 0, /* previous trigger depth */ false, /* startedInRecovery */ NULL /* link to parent state block */ }; *************** *** 3996,4001 **** CommitSubTransaction(void) --- 3998,4005 ---- */ XactReadOnly = s->prevXactReadOnly; + tg_depth = s->prevTgDepth; + CurrentResourceOwner = s->parent->curTransactionOwner; CurTransactionResourceOwner = s->parent->curTransactionOwner; ResourceOwnerDelete(s->curTransactionOwner); *************** *** 4114,4119 **** AbortSubTransaction(void) --- 4118,4125 ---- */ XactReadOnly = s->prevXactReadOnly; + tg_depth = s->prevTgDepth; + RESUME_INTERRUPTS(); } *************** *** 4196,4201 **** PushTransaction(void) --- 4202,4208 ---- s->blockState = TBLOCK_SUBBEGIN; GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); s->prevXactReadOnly = XactReadOnly; + s->prevTgDepth = tg_depth; CurrentTransactionState = s; *** a/src/backend/commands/trigger.c --- b/src/backend/commands/trigger.c *************** *** 55,60 **** --- 55,63 ---- #include "utils/tqual.h" + /* How many levels deep into trigger execution are we? */ + int tg_depth = 0; + /* GUC variables */ int SessionReplicationRole = SESSION_REPLICATION_ROLE_ORIGIN; *************** *** 1807,1812 **** ExecCallTriggerFunc(TriggerData *trigdata, --- 1810,1817 ---- if (instr) InstrStartNode(instr + tgindx); + tg_depth++; + /* * Do the function evaluation in the per-tuple memory context, so that * leaked memory will be reclaimed once per tuple. Note in particular that *************** *** 1828,1833 **** ExecCallTriggerFunc(TriggerData *trigdata, --- 1833,1840 ---- MemoryContextSwitchTo(oldContext); + tg_depth--; + /* * Trigger protocol allows function to return a null pointer, but NOT to * set the isnull result flag. *** a/src/backend/tcop/pquery.c --- b/src/backend/tcop/pquery.c *************** *** 748,753 **** PortalRun(Portal portal, long count, bool isTopLevel, --- 748,755 ---- errmsg("portal \"%s\" cannot be run", portal->name))); portal->status = PORTAL_ACTIVE; + tg_depth = 0; + /* * Set up global portal context pointers. * *************** *** 1371,1376 **** PortalRunFetch(Portal portal, --- 1373,1380 ---- errmsg("portal \"%s\" cannot be run", portal->name))); portal->status = PORTAL_ACTIVE; + tg_depth = 0; + /* * Set up global portal context pointers. */ *** a/src/include/commands/trigger.h --- b/src/include/commands/trigger.h *************** *** 108,113 **** extern PGDLLIMPORT int SessionReplicationRole; --- 108,115 ---- #define TRIGGER_FIRES_ON_REPLICA 'R' #define TRIGGER_DISABLED 'D' + extern int tg_depth; + extern Oid CreateTrigger(CreateTrigStmt *stmt, const char *queryString, Oid constraintOid, Oid indexOid, bool isInternal); *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** *** 633,638 **** do_compile(FunctionCallInfo fcinfo, --- 633,644 ---- true); function->tg_table_schema_varno = var->dno; + /* add the variable tg_depth */ + var = plpgsql_build_variable("tg_depth", 0, + plpgsql_build_datatype(INT4OID, -1), + true); + function->tg_depth_varno = var->dno; + /* Add the variable tg_nargs */ var = plpgsql_build_variable("tg_nargs", 0, plpgsql_build_datatype(INT4OID, -1), *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** *** 625,630 **** plpgsql_exec_trigger(PLpgSQL_function *func, --- 625,635 ---- var->isnull = false; var->freeval = true; + var = (PLpgSQL_var *) (estate.datums[func->tg_depth_varno]); + var->value = Int16GetDatum(tg_depth); + var->isnull = false; + var->freeval = false; + var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]); var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs); var->isnull = false; *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** *** 668,673 **** typedef struct PLpgSQL_function --- 668,674 ---- int tg_relname_varno; int tg_table_name_varno; int tg_table_schema_varno; + int tg_depth_varno; int tg_nargs_varno; int tg_argv_varno; *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** *** 4240,4242 **** select unreserved_test(); --- 4240,4317 ---- (1 row) drop function unreserved_test(); + -- Test TG_DEPTH + create table tg_depth_a (id int not null primary key); + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tg_depth_a_pkey" for table "tg_depth_a" + create table tg_depth_b (id int not null primary key); + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tg_depth_b_pkey" for table "tg_depth_b" + create table tg_depth_c (id int not null primary key); + NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "tg_depth_c_pkey" for table "tg_depth_c" + create function tg_depth_a_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + insert into tg_depth_b values (new.id); + raise notice '%: tg_depth = %', tg_name, tg_depth; + return new; + end; + $$; + create trigger tg_depth_a_tr before insert on tg_depth_a + for each row execute procedure tg_depth_a_tf(); + create function tg_depth_b_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + begin + execute 'insert into tg_depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: tg_depth = %', tg_depth; + end; + raise notice '%: tg_depth = %', tg_name, tg_depth; + execute 'insert into tg_depth_c values (' || new.id::text || ')'; + return new; + end; + $$; + create trigger tg_depth_b_tr before insert on tg_depth_b + for each row execute procedure tg_depth_b_tf(); + create function tg_depth_c_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + raise exception sqlstate 'U9999'; + return new; + end; + $$; + create trigger tg_depth_c_tr before insert on tg_depth_c + for each row execute procedure tg_depth_c_tf(); + insert into tg_depth_a values (999); + NOTICE: tg_depth_a_tr: tg_depth = 1 + NOTICE: tg_depth_b_tr: tg_depth = 2 + CONTEXT: SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + NOTICE: tg_depth_c_tr: tg_depth = 3 + CONTEXT: SQL statement "insert into tg_depth_c values (999)" + PL/pgSQL function "tg_depth_b_tf" line 5 at EXECUTE statement + SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + NOTICE: SQLSTATE = U9999: tg_depth = 2 + CONTEXT: SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + NOTICE: tg_depth_b_tr: tg_depth = 2 + CONTEXT: SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + NOTICE: tg_depth_c_tr: tg_depth = 3 + CONTEXT: SQL statement "insert into tg_depth_c values (999)" + PL/pgSQL function "tg_depth_b_tf" line 11 at EXECUTE statement + SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + ERROR: U9999 + CONTEXT: SQL statement "insert into tg_depth_c values (999)" + PL/pgSQL function "tg_depth_b_tf" line 11 at EXECUTE statement + SQL statement "insert into tg_depth_b values (new.id)" + PL/pgSQL function "tg_depth_a_tf" line 4 at SQL statement + drop table tg_depth_a, tg_depth_b, tg_depth_c; + drop function tg_depth_a_tf(); + drop function tg_depth_b_tf(); + drop function tg_depth_c_tf(); *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** *** 3375,3377 **** $$ language plpgsql; --- 3375,3431 ---- select unreserved_test(); drop function unreserved_test(); + + -- Test TG_DEPTH + + create table tg_depth_a (id int not null primary key); + create table tg_depth_b (id int not null primary key); + create table tg_depth_c (id int not null primary key); + + create function tg_depth_a_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + insert into tg_depth_b values (new.id); + raise notice '%: tg_depth = %', tg_name, tg_depth; + return new; + end; + $$; + create trigger tg_depth_a_tr before insert on tg_depth_a + for each row execute procedure tg_depth_a_tf(); + + create function tg_depth_b_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + begin + execute 'insert into tg_depth_c values (' || new.id::text || ')'; + exception + when sqlstate 'U9999' then + raise notice 'SQLSTATE = U9999: tg_depth = %', tg_depth; + end; + raise notice '%: tg_depth = %', tg_name, tg_depth; + execute 'insert into tg_depth_c values (' || new.id::text || ')'; + return new; + end; + $$; + create trigger tg_depth_b_tr before insert on tg_depth_b + for each row execute procedure tg_depth_b_tf(); + + create function tg_depth_c_tf() returns trigger + language plpgsql as $$ + begin + raise notice '%: tg_depth = %', tg_name, tg_depth; + raise exception sqlstate 'U9999'; + return new; + end; + $$; + create trigger tg_depth_c_tr before insert on tg_depth_c + for each row execute procedure tg_depth_c_tf(); + + insert into tg_depth_a values (999); + + drop table tg_depth_a, tg_depth_b, tg_depth_c; + drop function tg_depth_a_tf(); + drop function tg_depth_b_tf(); + drop function tg_depth_c_tf();