The people who write my paychecks have insisted on me chunking out
some items which are part of our long-term plan besides the one I've
been focusing on lately. Most of it isn't of interest to anyone
outside Wisconsin Courts, but this piece might be; so I'm posting it
and putting onto the first 9.2 CF. We'll be using it for
development starting Monday and in production in two or three
months, so it should be pretty well tested by July. :-)
-Kevin
*** 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 ----
</varlistentry>
<varlistentry>
+ <term><varname>TG_DEPTH</varname></term>
+ <listitem>
+ <para>
+ Data type <type>integer</type>; the current number of levels of
+ nesting within trigger execution.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>TG_NARGS</varname></term>
<listitem>
<para>
*** 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();
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers