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 (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to