diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index eea6ec5..5be8086 100644 *** a/doc/src/sgml/plpgsql.sgml --- b/doc/src/sgml/plpgsql.sgml *************** RAISE unique_violation USING MESSAGE = ' *** 3383,3388 **** --- 3383,3398 ---- + TG_DEPTH + + + Data type integer; the current number of levels of + nesting within trigger execution. + + + + + TG_NARGS diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 2ca1c14..273fcae 100644 *** a/src/backend/access/transam/xact.c --- b/src/backend/access/transam/xact.c *************** typedef struct TransactionStateData *** 147,152 **** --- 147,153 ---- 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; *************** static TransactionStateData TopTransacti *** 176,181 **** --- 177,183 ---- 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 */ }; *************** CommitSubTransaction(void) *** 4039,4044 **** --- 4041,4048 ---- */ XactReadOnly = s->prevXactReadOnly; + tg_depth = s->prevTgDepth; + CurrentResourceOwner = s->parent->curTransactionOwner; CurTransactionResourceOwner = s->parent->curTransactionOwner; ResourceOwnerDelete(s->curTransactionOwner); *************** AbortSubTransaction(void) *** 4157,4162 **** --- 4161,4168 ---- */ XactReadOnly = s->prevXactReadOnly; + tg_depth = s->prevTgDepth; + RESUME_INTERRUPTS(); } *************** PushTransaction(void) *** 4239,4244 **** --- 4245,4251 ---- s->blockState = TBLOCK_SUBBEGIN; GetUserIdAndSecContext(&s->prevUser, &s->prevSecContext); s->prevXactReadOnly = XactReadOnly; + s->prevTgDepth = tg_depth; CurrentTransactionState = s; diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index ce36ea8..a9cb0a0 100644 *** a/src/backend/commands/trigger.c --- b/src/backend/commands/trigger.c *************** *** 56,61 **** --- 56,64 ---- #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; *************** ExecCallTriggerFunc(TriggerData *trigdat *** 1811,1816 **** --- 1814,1821 ---- 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 *************** ExecCallTriggerFunc(TriggerData *trigdat *** 1833,1838 **** --- 1838,1845 ---- MemoryContextSwitchTo(oldContext); + tg_depth--; + /* * Trigger protocol allows function to return a null pointer, but NOT to * set the isnull result flag. diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index b7649c6..a784b32 100644 *** a/src/backend/tcop/pquery.c --- b/src/backend/tcop/pquery.c *************** PortalRun(Portal portal, long count, boo *** 738,743 **** --- 738,745 ---- errmsg("portal \"%s\" cannot be run", portal->name))); portal->status = PORTAL_ACTIVE; + tg_depth = 0; + /* * Set up global portal context pointers. * *************** PortalRunFetch(Portal portal, *** 1401,1406 **** --- 1403,1410 ---- errmsg("portal \"%s\" cannot be run", portal->name))); portal->status = PORTAL_ACTIVE; + tg_depth = 0; + /* * Set up global portal context pointers. */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index ad97871..349341e 100644 *** a/src/include/commands/trigger.h --- b/src/include/commands/trigger.h *************** extern PGDLLIMPORT int SessionReplicatio *** 108,113 **** --- 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); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 75098ec..83c60e8 100644 *** a/src/pl/plpgsql/src/pl_comp.c --- b/src/pl/plpgsql/src/pl_comp.c *************** do_compile(FunctionCallInfo fcinfo, *** 655,660 **** --- 655,668 ---- 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, + InvalidOid), + true); + function->tg_depth_varno = var->dno; + /* Add the variable tg_nargs */ var = plpgsql_build_variable("tg_nargs", 0, plpgsql_build_datatype(INT4OID, diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 906a485..13f7238 100644 *** a/src/pl/plpgsql/src/pl_exec.c --- b/src/pl/plpgsql/src/pl_exec.c *************** plpgsql_exec_trigger(PLpgSQL_function *f *** 627,632 **** --- 627,637 ---- 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; diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index 89103ae..a5512fb 100644 *** a/src/pl/plpgsql/src/plpgsql.h --- b/src/pl/plpgsql/src/plpgsql.h *************** typedef struct PLpgSQL_function *** 690,695 **** --- 690,696 ---- 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; diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out index bfabcbc..1f36944 100644 *** a/src/test/regress/expected/plpgsql.out --- b/src/test/regress/expected/plpgsql.out *************** NOTICE: {"(35,78)","(88,76)"} *** 4434,4436 **** --- 4434,4511 ---- drop function foreach_test(anyarray); drop type xy_tuple; + -- 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(); diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql index 14fb457..46ebef1 100644 *** a/src/test/regress/sql/plpgsql.sql --- b/src/test/regress/sql/plpgsql.sql *************** select foreach_test(ARRAY[[(10,20),(40,6 *** 3489,3491 **** --- 3489,3547 ---- drop function foreach_test(anyarray); drop type xy_tuple; + + + -- 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(); +