plpgsql function startup-time improvements

Поиск
Список
Период
Сортировка
От Tom Lane
Тема plpgsql function startup-time improvements
Дата
Msg-id 11986.1514407114@sss.pgh.pa.us
обсуждение исходный текст
Ответы Re: plpgsql function startup-time improvements  (Peter Eisentraut <peter.eisentraut@2ndquadrant.com>)
Re: plpgsql function startup-time improvements  ("Tels" <nospam-pg-abuse@bloodgate.com>)
Re: plpgsql function startup-time improvements  (Pavel Stehule <pavel.stehule@gmail.com>)
Список pgsql-hackers
Attached are patches for two performance-improvement ideas that came
to me while working on
https://www.postgresql.org/message-id/8962.1514399547@sss.pgh.pa.us
The three patches are logically independent and could be committed in
any order.  But they touch some overlapping code, so as presented,
you need to apply that other patch first and then these two in
sequence.

The motivation for the first patch is that I noticed that for simple
plpgsql functions, especially triggers, the per-datum palloc()s performed
by copy_plpgsql_datum() during function entry amounted to a significant
fraction of the total runtime.  To fix that, the patch simply does one
palloc for the space needed by all datums, relying on a space calculation
performed at the end of function compilation by plpgsql_finish_datums().
This does nothing much for trivial functions with only a few datums, but
for ones with more, it's a worthwhile savings.

BTW, I experimented with a more drastic solution involving separating
the "read only" and "writable" parts of PLpgSQL_datum structs and then
instantiating only the "writable" parts, thus considerably reducing the
amount of data to be copied during per-call initialization.  But that
was a lot more invasive to the code, and it seemed to be slower than
what I present here, because performance-critical accesses to variables
had to compute the addresses of both structs associated with the variable.
I've not totally given up hope for that idea, but it'll require more
tuning than I had time for.

In addition to the core idea of the patch, I noticed that there is no
good reason for PLpgSQL_expr to be treated as a kind of PLpgSQL_datum;
those structs are never members of the estate->datums[] array, nor is
there any code expecting them to be structural supersets of PLpgSQL_datum.
So the patch also removes PLPGSQL_DTYPE_EXPR and the useless fields of
PLpgSQL_expr.

Also, I changed PLpgSQL_var.isconst and PLpgSQL_var.notnull from "int"
to "bool", which is what they should have been all along, and relocated
them in the PLpgSQL_var struct.  There are two motivations for this.
It saves a whole 8 bytes per PLpgSQL_var, at least on 64-bit machines,
because the fields now fit into what had been wasted padding space;
reducing the size of what we have to copy during copy_plpgsql_datums
has to be worth something.  Second, those fields are now adjacent to
the common PLpgSQL_variable fields, which will simplify migrating them
into PLpgSQL_variable, as I anticipate we'll want to do at some point
when we allow composite variables to be marked CONSTANT and maybe NOT
NULL.


The idea of the second patch came from noticing that in simple trigger
functions, quite a large fraction of cycles went into setting up the
"built in" variables such as tg_name, even though many trigger functions
probably never read most of those variables.  We could improve that by
not computing the values until/unless they're read.  There are various
names for this technique, but the one that seemed most evocative to me
was to say that these variables have "promises" attached to them.  So
that's what the patch calls them.  We mark the variables with an enum
indicating which promise needs to be fulfilled for each one, and then
when about to read a datum, we fulfill the promise if needed.

The method I settled on for that was to invent a separate DTYPE_PROMISE,
which otherwise is treated exactly like DTYPE_VAR, and to code places
like exec_eval_datum() like this:
  
    switch (datum->dtype)
    {
+       case PLPGSQL_DTYPE_PROMISE:
+           /* fulfill promise if needed, then handle like regular var */
+           plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum);
+ 
+           /* FALL THRU */
+ 
        case PLPGSQL_DTYPE_VAR:
            {
                PLpgSQL_var *var = (PLpgSQL_var *) datum;

The extra DTYPE is a little bit grotty, but it's not awful.  One
alternative I experimented with was to just treat these variables
as plain DTYPE_VAR, requiring coding like

        case PLPGSQL_DTYPE_VAR:
            {
                PLpgSQL_var *var = (PLpgSQL_var *) datum;
  
+               if (unlikely(var->promise != PLPGSQL_PROMISE_NONE))
+                   plpgsql_fulfill_promise(estate, var);
+ 
                *typeid = var->datatype->typoid;
                *typetypmod = var->datatype->atttypmod;
                *value = var->value;

However, this way is injecting an additional test-and-branch into
hot code paths, and it was demonstrably slower.

With these patches, I see performance improvements of 10% to 20%
on simple but not totally unrealistic triggers, for example

create or replace function mytrig() returns trigger language plpgsql as
$$
begin
  if (new.f1 != new.f2) or (new.f3 != new.f4) then
    new.f3 = 42;
  end if;
  return new;
end$$ stable;

(BTW, those are percentages of total INSERT runtime, not just of
the trigger proper; though I cheated to the extent of using a
temp not regular table.)

It seems possible that the "promise" technique could be useful for
other plpgsql special variables in future.  I thought briefly about
applying it to triggers' NEW and OLD arguments, but desisted because
(a) it's only a win if triggers will commonly not touch the variable,
which seems unlikely to be true for NEW/OLD; and (b) it would have
required infrastructure for attaching a promise to a DTYPE_REC
variable, which was more pain than I wanted.  But I wonder if it'd
be useful for, say, the special variables that exception blocks create.

I'll add this to the January commitfest.

            regards, tom lane

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 7d966e7..ca7d152 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** plpgsql_adddatum(PLpgSQL_datum *new)
*** 2181,2192 ****
--- 2181,2209 ----
  static void
  plpgsql_finish_datums(PLpgSQL_function *function)
  {
+     Size        copiable_size = 0;
      int            i;

      function->ndatums = plpgsql_nDatums;
      function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
      for (i = 0; i < plpgsql_nDatums; i++)
+     {
          function->datums[i] = plpgsql_Datums[i];
+
+         /* This must agree with copy_plpgsql_datums on what is copiable */
+         switch (function->datums[i]->dtype)
+         {
+             case PLPGSQL_DTYPE_VAR:
+                 copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
+                 break;
+             case PLPGSQL_DTYPE_REC:
+                 copiable_size += MAXALIGN(sizeof(PLpgSQL_rec));
+                 break;
+             default:
+                 break;
+         }
+     }
+     function->copiable_size = copiable_size;
  }


diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index a0a10f2..e0ed19f 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static HTAB *shared_cast_hash = NULL;
*** 161,167 ****
  static void coerce_function_result_tuple(PLpgSQL_execstate *estate,
                               TupleDesc tupdesc);
  static void plpgsql_exec_error_callback(void *arg);
! static PLpgSQL_datum *copy_plpgsql_datum(PLpgSQL_datum *datum);
  static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
  static void push_stmt_mcontext(PLpgSQL_execstate *estate);
  static void pop_stmt_mcontext(PLpgSQL_execstate *estate);
--- 161,168 ----
  static void coerce_function_result_tuple(PLpgSQL_execstate *estate,
                               TupleDesc tupdesc);
  static void plpgsql_exec_error_callback(void *arg);
! static void copy_plpgsql_datums(PLpgSQL_execstate *estate,
!                     PLpgSQL_function *func);
  static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
  static void push_stmt_mcontext(PLpgSQL_execstate *estate);
  static void pop_stmt_mcontext(PLpgSQL_execstate *estate);
*************** plpgsql_exec_function(PLpgSQL_function *
*** 380,387 ****
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     for (i = 0; i < estate.ndatums; i++)
!         estate.datums[i] = copy_plpgsql_datum(func->datums[i]);

      /*
       * Store the actual call argument values into the appropriate variables
--- 381,387 ----
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     copy_plpgsql_datums(&estate, func);

      /*
       * Store the actual call argument values into the appropriate variables
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 772,779 ****
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     for (i = 0; i < estate.ndatums; i++)
!         estate.datums[i] = copy_plpgsql_datum(func->datums[i]);

      /*
       * Put the OLD and NEW tuples into record variables
--- 772,778 ----
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     copy_plpgsql_datums(&estate, func);

      /*
       * Put the OLD and NEW tuples into record variables
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1066,1072 ****
  {
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
-     int            i;
      int            rc;
      PLpgSQL_var *var;

--- 1065,1070 ----
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1087,1094 ****
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     for (i = 0; i < estate.ndatums; i++)
!         estate.datums[i] = copy_plpgsql_datum(func->datums[i]);

      /*
       * Assign the special tg_ variables
--- 1085,1091 ----
       * Make local execution copies of all the datums
       */
      estate.err_text = gettext_noop("during initialization of execution state");
!     copy_plpgsql_datums(&estate, func);

      /*
       * Assign the special tg_ variables
*************** plpgsql_exec_error_callback(void *arg)
*** 1203,1259 ****
   * Support function for initializing local execution variables
   * ----------
   */
! static PLpgSQL_datum *
! copy_plpgsql_datum(PLpgSQL_datum *datum)
  {
!     PLpgSQL_datum *result;

!     switch (datum->dtype)
!     {
!         case PLPGSQL_DTYPE_VAR:
!             {
!                 PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));

!                 memcpy(new, datum, sizeof(PLpgSQL_var));
!                 /* should be preset to null/non-freeable */
!                 Assert(new->isnull);
!                 Assert(!new->freeval);

!                 result = (PLpgSQL_datum *) new;
!             }
!             break;

!         case PLPGSQL_DTYPE_REC:
!             {
!                 PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));

!                 memcpy(new, datum, sizeof(PLpgSQL_rec));
!                 /* should be preset to empty */
!                 Assert(new->erh == NULL);

!                 result = (PLpgSQL_datum *) new;
!             }
!             break;

!         case PLPGSQL_DTYPE_ROW:
!         case PLPGSQL_DTYPE_RECFIELD:
!         case PLPGSQL_DTYPE_ARRAYELEM:

!             /*
!              * These datum records are read-only at runtime, so no need to
!              * copy them (well, RECFIELD and ARRAYELEM contain cached data,
!              * but we'd just as soon centralize the caching anyway)
!              */
!             result = datum;
!             break;

!         default:
!             elog(ERROR, "unrecognized dtype: %d", datum->dtype);
!             result = NULL;        /* keep compiler quiet */
!             break;
      }

!     return result;
  }

  /*
--- 1200,1272 ----
   * Support function for initializing local execution variables
   * ----------
   */
! static void
! copy_plpgsql_datums(PLpgSQL_execstate *estate,
!                     PLpgSQL_function *func)
  {
!     int            ndatums = estate->ndatums;
!     PLpgSQL_datum **indatums;
!     PLpgSQL_datum **outdatums;
!     char       *workspace;
!     char       *ws_next;
!     int            i;

!     /* Allocate local datum-pointer array */
!     estate->datums = (PLpgSQL_datum **)
!         palloc(sizeof(PLpgSQL_datum *) * ndatums);

!     /*
!      * To reduce palloc overhead, we make a single palloc request for all the
!      * space needed for locally-instantiated datums.
!      */
!     workspace = palloc(func->copiable_size);
!     ws_next = workspace;

!     /* Fill datum-pointer array, copying datums into workspace as needed */
!     indatums = func->datums;
!     outdatums = estate->datums;
!     for (i = 0; i < ndatums; i++)
!     {
!         PLpgSQL_datum *indatum = indatums[i];
!         PLpgSQL_datum *outdatum;

!         /* This must agree with plpgsql_finish_datums on what is copiable */
!         switch (indatum->dtype)
!         {
!             case PLPGSQL_DTYPE_VAR:
!                 outdatum = (PLpgSQL_datum *) ws_next;
!                 memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
!                 ws_next += MAXALIGN(sizeof(PLpgSQL_var));
!                 break;

!             case PLPGSQL_DTYPE_REC:
!                 outdatum = (PLpgSQL_datum *) ws_next;
!                 memcpy(outdatum, indatum, sizeof(PLpgSQL_rec));
!                 ws_next += MAXALIGN(sizeof(PLpgSQL_rec));
!                 break;

!             case PLPGSQL_DTYPE_ROW:
!             case PLPGSQL_DTYPE_RECFIELD:
!             case PLPGSQL_DTYPE_ARRAYELEM:

!                 /*
!                  * These datum records are read-only at runtime, so no need to
!                  * copy them (well, RECFIELD and ARRAYELEM contain cached
!                  * data, but we'd just as soon centralize the caching anyway).
!                  */
!                 outdatum = indatum;
!                 break;

!             default:
!                 elog(ERROR, "unrecognized dtype: %d", indatum->dtype);
!                 outdatum = NULL;    /* keep compiler quiet */
!                 break;
!         }

!         outdatums[i] = outdatum;
      }

!     Assert(ws_next == workspace + func->copiable_size);
  }

  /*
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3560,3567 ****

      estate->found_varno = func->found_varno;
      estate->ndatums = func->ndatums;
!     estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
!     /* caller is expected to fill the datums array */
      estate->datum_context = CurrentMemoryContext;

      /* initialize our ParamListInfo with appropriate hook functions */
--- 3573,3580 ----

      estate->found_varno = func->found_varno;
      estate->ndatums = func->ndatums;
!     estate->datums = NULL;
!     /* the datums array will be filled by copy_plpgsql_datums() */
      estate->datum_context = CurrentMemoryContext;

      /* initialize our ParamListInfo with appropriate hook functions */
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 3bd2493..df4694b 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** decl_statement    : decl_varname decl_const
*** 561,567 ****

                          curname_def = palloc0(sizeof(PLpgSQL_expr));

-                         curname_def->dtype = PLPGSQL_DTYPE_EXPR;
                          strcpy(buf, "SELECT ");
                          cp1 = new->refname;
                          cp2 = buf + strlen(buf);
--- 561,566 ----
*************** read_sql_construct(int until,
*** 2663,2669 ****
      }

      expr = palloc0(sizeof(PLpgSQL_expr));
-     expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
      expr->paramnos        = NULL;
--- 2662,2667 ----
*************** make_execsql_stmt(int firsttoken, int lo
*** 2910,2916 ****
          ds.data[--ds.len] = '\0';

      expr = palloc0(sizeof(PLpgSQL_expr));
-     expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
      expr->paramnos        = NULL;
--- 2908,2913 ----
*************** read_cursor_args(PLpgSQL_var *cursor, in
*** 3782,3788 ****
      appendStringInfoChar(&ds, ';');

      expr = palloc0(sizeof(PLpgSQL_expr));
-     expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
      expr->paramnos        = NULL;
--- 3779,3784 ----
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 21dc41f..01c14e3 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef enum PLpgSQL_datum_type
*** 63,70 ****
      PLPGSQL_DTYPE_ROW,
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
!     PLPGSQL_DTYPE_ARRAYELEM,
!     PLPGSQL_DTYPE_EXPR
  } PLpgSQL_datum_type;

  /*
--- 63,69 ----
      PLPGSQL_DTYPE_ROW,
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
!     PLPGSQL_DTYPE_ARRAYELEM
  } PLpgSQL_datum_type;

  /*
*************** typedef struct PLpgSQL_type
*** 187,224 ****
  } PLpgSQL_type;

  /*
-  * Generic datum array item
-  *
-  * PLpgSQL_datum is the common supertype for PLpgSQL_expr, PLpgSQL_var,
-  * PLpgSQL_row, PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem
-  */
- typedef struct PLpgSQL_datum
- {
-     PLpgSQL_datum_type dtype;
-     int            dno;
- } PLpgSQL_datum;
-
- /*
-  * Scalar or composite variable
-  *
-  * The variants PLpgSQL_var, PLpgSQL_row, and PLpgSQL_rec share these
-  * fields
-  */
- typedef struct PLpgSQL_variable
- {
-     PLpgSQL_datum_type dtype;
-     int            dno;
-     char       *refname;
-     int            lineno;
- } PLpgSQL_variable;
-
- /*
   * SQL Query to plan and execute
   */
  typedef struct PLpgSQL_expr
  {
-     PLpgSQL_datum_type dtype;
-     int            dno;
      char       *query;
      SPIPlanPtr    plan;
      Bitmapset  *paramnos;        /* all dnos referenced by this query */
--- 186,195 ----
*************** typedef struct PLpgSQL_expr
*** 248,253 ****
--- 219,250 ----
  } PLpgSQL_expr;

  /*
+  * Generic datum array item
+  *
+  * PLpgSQL_datum is the common supertype for PLpgSQL_var, PLpgSQL_row,
+  * PLpgSQL_rec, PLpgSQL_recfield, and PLpgSQL_arrayelem.
+  */
+ typedef struct PLpgSQL_datum
+ {
+     PLpgSQL_datum_type dtype;
+     int            dno;
+ } PLpgSQL_datum;
+
+ /*
+  * Scalar or composite variable
+  *
+  * The variants PLpgSQL_var, PLpgSQL_row, and PLpgSQL_rec share these
+  * fields.
+  */
+ typedef struct PLpgSQL_variable
+ {
+     PLpgSQL_datum_type dtype;
+     int            dno;
+     char       *refname;
+     int            lineno;
+ } PLpgSQL_variable;
+
+ /*
   * Scalar variable
   */
  typedef struct PLpgSQL_var
*************** typedef struct PLpgSQL_var
*** 256,266 ****
      int            dno;
      char       *refname;
      int            lineno;

      PLpgSQL_type *datatype;
-     int            isconst;
-     int            notnull;
      PLpgSQL_expr *default_val;
      PLpgSQL_expr *cursor_explicit_expr;
      int            cursor_explicit_argrow;
      int            cursor_options;
--- 253,270 ----
      int            dno;
      char       *refname;
      int            lineno;
+     /* end of PLpgSQL_variable fields */

+     bool        isconst;
+     bool        notnull;
      PLpgSQL_type *datatype;
      PLpgSQL_expr *default_val;
+
+     /*
+      * Variables declared as CURSOR FOR <query> are mostly like ordinary
+      * scalar variables of type refcursor, but they have these additional
+      * properties:
+      */
      PLpgSQL_expr *cursor_explicit_expr;
      int            cursor_explicit_argrow;
      int            cursor_options;
*************** typedef struct PLpgSQL_row
*** 284,289 ****
--- 288,294 ----
      int            dno;
      char       *refname;
      int            lineno;
+     /* end of PLpgSQL_variable fields */

      /*
       * rowtupdesc is only set up if we might need to convert the row into a
*************** typedef struct PLpgSQL_rec
*** 306,311 ****
--- 311,318 ----
      int            dno;
      char       *refname;
      int            lineno;
+     /* end of PLpgSQL_variable fields */
+
      Oid            rectypeid;        /* declared type of variable */
      /* RECFIELDs for this record are chained together for easy access */
      int            firstfield;        /* dno of first RECFIELD, or -1 if none */
*************** typedef struct PLpgSQL_recfield
*** 320,325 ****
--- 327,334 ----
  {
      PLpgSQL_datum_type dtype;
      int            dno;
+     /* end of PLpgSQL_datum fields */
+
      char       *fieldname;        /* name of field */
      int            recparentno;    /* dno of parent record */
      int            nextfield;        /* dno of next child, or -1 if none */
*************** typedef struct PLpgSQL_arrayelem
*** 335,340 ****
--- 344,351 ----
  {
      PLpgSQL_datum_type dtype;
      int            dno;
+     /* end of PLpgSQL_datum fields */
+
      PLpgSQL_expr *subscript;
      int            arrayparentno;    /* dno of parent array variable */

*************** typedef struct PLpgSQL_function
*** 864,869 ****
--- 875,881 ----
      /* the datums representing the function's local variables */
      int            ndatums;
      PLpgSQL_datum **datums;
+     Size        copiable_size;    /* space for locally instantiated datums */

      /* function body parsetree */
      PLpgSQL_stmt_block *action;
*************** typedef struct PLpgSQL_execstate
*** 900,907 ****
      ResourceOwner tuple_store_owner;
      ReturnSetInfo *rsi;

-     /* the datums representing the function's local variables */
      int            found_varno;
      int            ndatums;
      PLpgSQL_datum **datums;
      /* context containing variable values (same as func's SPI_proc context) */
--- 912,925 ----
      ResourceOwner tuple_store_owner;
      ReturnSetInfo *rsi;

      int            found_varno;
+
+     /*
+      * The datums representing the function's local variables.  Some of these
+      * are local storage in this execstate, but some just point to the shared
+      * copy belonging to the PLpgSQL_function, depending on whether or not we
+      * need any per-execution state for the datum's dtype.
+      */
      int            ndatums;
      PLpgSQL_datum **datums;
      /* context containing variable values (same as func's SPI_proc context) */
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index ca7d152..4ce6613 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 610,616 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_name_varno = var->dno;

              /* Add the variable tg_when */
              var = plpgsql_build_variable("tg_when", 0,
--- 610,618 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NAME;

              /* Add the variable tg_when */
              var = plpgsql_build_variable("tg_when", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 618,624 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_when_varno = var->dno;

              /* Add the variable tg_level */
              var = plpgsql_build_variable("tg_level", 0,
--- 620,628 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_WHEN;

              /* Add the variable tg_level */
              var = plpgsql_build_variable("tg_level", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 626,632 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_level_varno = var->dno;

              /* Add the variable tg_op */
              var = plpgsql_build_variable("tg_op", 0,
--- 630,638 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_LEVEL;

              /* Add the variable tg_op */
              var = plpgsql_build_variable("tg_op", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 634,640 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_op_varno = var->dno;

              /* Add the variable tg_relid */
              var = plpgsql_build_variable("tg_relid", 0,
--- 640,648 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_OP;

              /* Add the variable tg_relid */
              var = plpgsql_build_variable("tg_relid", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 642,648 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_relid_varno = var->dno;

              /* Add the variable tg_relname */
              var = plpgsql_build_variable("tg_relname", 0,
--- 650,658 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_RELID;

              /* Add the variable tg_relname */
              var = plpgsql_build_variable("tg_relname", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 650,656 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_relname_varno = var->dno;

              /* tg_table_name is now preferred to tg_relname */
              var = plpgsql_build_variable("tg_table_name", 0,
--- 660,668 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;

              /* tg_table_name is now preferred to tg_relname */
              var = plpgsql_build_variable("tg_table_name", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 658,664 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_table_name_varno = var->dno;

              /* add the variable tg_table_schema */
              var = plpgsql_build_variable("tg_table_schema", 0,
--- 670,678 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;

              /* add the variable tg_table_schema */
              var = plpgsql_build_variable("tg_table_schema", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 666,672 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_table_schema_varno = var->dno;

              /* Add the variable tg_nargs */
              var = plpgsql_build_variable("tg_nargs", 0,
--- 680,688 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_SCHEMA;

              /* Add the variable tg_nargs */
              var = plpgsql_build_variable("tg_nargs", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 674,680 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_nargs_varno = var->dno;

              /* Add the variable tg_argv */
              var = plpgsql_build_variable("tg_argv", 0,
--- 690,698 ----
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NARGS;

              /* Add the variable tg_argv */
              var = plpgsql_build_variable("tg_argv", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 682,688 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_argv_varno = var->dno;

              break;

--- 700,708 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;

              break;

*************** do_compile(FunctionCallInfo fcinfo,
*** 704,710 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_event_varno = var->dno;

              /* Add the variable tg_tag */
              var = plpgsql_build_variable("tg_tag", 0,
--- 724,732 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_EVENT;

              /* Add the variable tg_tag */
              var = plpgsql_build_variable("tg_tag", 0,
*************** do_compile(FunctionCallInfo fcinfo,
*** 712,718 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_tag_varno = var->dno;

              break;

--- 734,742 ----
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             Assert(var->dtype == PLPGSQL_DTYPE_VAR);
!             var->dtype = PLPGSQL_DTYPE_PROMISE;
!             ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;

              break;

*************** build_row_from_vars(PLpgSQL_variable **v
*** 1881,1886 ****
--- 1905,1911 ----
          switch (var->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  typoid = ((PLpgSQL_var *) var)->datatype->typoid;
                  typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
                  typcoll = ((PLpgSQL_var *) var)->datatype->collation;
*************** plpgsql_finish_datums(PLpgSQL_function *
*** 2194,2199 ****
--- 2219,2225 ----
          switch (function->datums[i]->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
                  break;
              case PLPGSQL_DTYPE_REC:
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index e0ed19f..0ed95cb 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static void coerce_function_result_tuple
*** 163,168 ****
--- 163,170 ----
  static void plpgsql_exec_error_callback(void *arg);
  static void copy_plpgsql_datums(PLpgSQL_execstate *estate,
                      PLpgSQL_function *func);
+ static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
+                         PLpgSQL_var *var);
  static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
  static void push_stmt_mcontext(PLpgSQL_execstate *estate);
  static void pop_stmt_mcontext(PLpgSQL_execstate *estate);
*************** plpgsql_exec_function(PLpgSQL_function *
*** 469,474 ****
--- 471,477 ----
                  break;

              default:
+                 /* Anything else should not be an argument variable */
                  elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
          }
      }
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 747,756 ****
  {
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
-     int            i;
      int            rc;
      TupleDesc    tupdesc;
-     PLpgSQL_var *var;
      PLpgSQL_rec *rec_new,
                 *rec_old;
      HeapTuple    rettup;
--- 750,757 ----
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 759,764 ****
--- 760,766 ----
       * Setup the execution state
       */
      plpgsql_estate_setup(&estate, func, NULL, NULL);
+     estate.trigdata = trigdata;

      /*
       * Setup error traceback support for ereport()
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 819,924 ****
      rc = SPI_register_trigger_data(trigdata);
      Assert(rc >= 0);

-     /*
-      * Assign the special tg_ variables
-      */
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
-     if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
-         assign_text_var(&estate, var, "INSERT");
-     else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
-         assign_text_var(&estate, var, "UPDATE");
-     else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
-         assign_text_var(&estate, var, "DELETE");
-     else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
-         assign_text_var(&estate, var, "TRUNCATE");
-     else
-         elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
-     assign_simple_var(&estate, var,
-                       DirectFunctionCall1(namein,
-                                           CStringGetDatum(trigdata->tg_trigger->tgname)),
-                       false, true);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
-     if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
-         assign_text_var(&estate, var, "BEFORE");
-     else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
-         assign_text_var(&estate, var, "AFTER");
-     else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
-         assign_text_var(&estate, var, "INSTEAD OF");
-     else
-         elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
-     if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
-         assign_text_var(&estate, var, "ROW");
-     else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
-         assign_text_var(&estate, var, "STATEMENT");
-     else
-         elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
-     assign_simple_var(&estate, var,
-                       ObjectIdGetDatum(trigdata->tg_relation->rd_id),
-                       false, false);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
-     assign_simple_var(&estate, var,
-                       DirectFunctionCall1(namein,
-                                           CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))),
-                       false, true);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_table_name_varno]);
-     assign_simple_var(&estate, var,
-                       DirectFunctionCall1(namein,
-                                           CStringGetDatum(RelationGetRelationName(trigdata->tg_relation))),
-                       false, true);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_table_schema_varno]);
-     assign_simple_var(&estate, var,
-                       DirectFunctionCall1(namein,
-                                           CStringGetDatum(get_namespace_name(
-                                                                              RelationGetNamespace(
-
trigdata->tg_relation)))),
-                       false, true);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]);
-     assign_simple_var(&estate, var,
-                       Int16GetDatum(trigdata->tg_trigger->tgnargs),
-                       false, false);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
-     if (trigdata->tg_trigger->tgnargs > 0)
-     {
-         /*
-          * For historical reasons, tg_argv[] subscripts start at zero not one.
-          * So we can't use construct_array().
-          */
-         int            nelems = trigdata->tg_trigger->tgnargs;
-         Datum       *elems;
-         int            dims[1];
-         int            lbs[1];
-
-         elems = palloc(sizeof(Datum) * nelems);
-         for (i = 0; i < nelems; i++)
-             elems[i] = CStringGetTextDatum(trigdata->tg_trigger->tgargs[i]);
-         dims[0] = nelems;
-         lbs[0] = 0;
-
-         assign_simple_var(&estate, var,
-                           PointerGetDatum(construct_md_array(elems, NULL,
-                                                              1, dims, lbs,
-                                                              TEXTOID,
-                                                              -1, false, 'i')),
-                           false, true);
-     }
-     else
-     {
-         assign_simple_var(&estate, var, (Datum) 0, true, false);
-     }
-
      estate.err_text = gettext_noop("during function entry");

      /*
--- 821,826 ----
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1066,1077 ****
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
      int            rc;
-     PLpgSQL_var *var;

      /*
       * Setup the execution state
       */
      plpgsql_estate_setup(&estate, func, NULL, NULL);

      /*
       * Setup error traceback support for ereport()
--- 968,979 ----
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
      int            rc;

      /*
       * Setup the execution state
       */
      plpgsql_estate_setup(&estate, func, NULL, NULL);
+     estate.evtrigdata = trigdata;

      /*
       * Setup error traceback support for ereport()
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1088,1102 ****
      copy_plpgsql_datums(&estate, func);

      /*
-      * Assign the special tg_ variables
-      */
-     var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]);
-     assign_text_var(&estate, var, trigdata->event);
-
-     var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
-     assign_text_var(&estate, var, trigdata->tag);
-
-     /*
       * Let the instrumentation plugin peek at this function
       */
      if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
--- 990,995 ----
*************** copy_plpgsql_datums(PLpgSQL_execstate *e
*** 1234,1239 ****
--- 1127,1133 ----
          switch (indatum->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  outdatum = (PLpgSQL_datum *) ws_next;
                  memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
                  ws_next += MAXALIGN(sizeof(PLpgSQL_var));
*************** copy_plpgsql_datums(PLpgSQL_execstate *e
*** 1270,1275 ****
--- 1164,1329 ----
  }

  /*
+  * If the variable has an armed "promise", compute the promised value
+  * and assign it to the variable.
+  * The assignment automatically disarms the promise.
+  */
+ static void
+ plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
+                         PLpgSQL_var *var)
+ {
+     MemoryContext oldcontext;
+
+     if (var->promise == PLPGSQL_PROMISE_NONE)
+         return;                    /* nothing to do */
+
+     /*
+      * This will typically be invoked in a short-lived context such as the
+      * mcontext.  We must create variable values in the estate's datum
+      * context.  This quick-and-dirty solution risks leaking some additional
+      * cruft there, but since any one promise is honored at most once per
+      * function call, it's probably not worth being more careful.
+      */
+     oldcontext = MemoryContextSwitchTo(estate->datum_context);
+
+     switch (var->promise)
+     {
+         case PLPGSQL_PROMISE_TG_NAME:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             assign_simple_var(estate, var,
+                               DirectFunctionCall1(namein,
+                                                   CStringGetDatum(estate->trigdata->tg_trigger->tgname)),
+                               false, true);
+             break;
+
+         case PLPGSQL_PROMISE_TG_WHEN:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "BEFORE");
+             else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "AFTER");
+             else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "INSTEAD OF");
+             else
+                 elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
+             break;
+
+         case PLPGSQL_PROMISE_TG_LEVEL:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "ROW");
+             else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "STATEMENT");
+             else
+                 elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
+             break;
+
+         case PLPGSQL_PROMISE_TG_OP:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "INSERT");
+             else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "UPDATE");
+             else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "DELETE");
+             else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event))
+                 assign_text_var(estate, var, "TRUNCATE");
+             else
+                 elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
+             break;
+
+         case PLPGSQL_PROMISE_TG_RELID:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             assign_simple_var(estate, var,
+                               ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id),
+                               false, false);
+             break;
+
+         case PLPGSQL_PROMISE_TG_TABLE_NAME:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             assign_simple_var(estate, var,
+                               DirectFunctionCall1(namein,
+
CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))),
+                               false, true);
+             break;
+
+         case PLPGSQL_PROMISE_TG_TABLE_SCHEMA:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             assign_simple_var(estate, var,
+                               DirectFunctionCall1(namein,
+
CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))),
+                               false, true);
+             break;
+
+         case PLPGSQL_PROMISE_TG_NARGS:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             assign_simple_var(estate, var,
+                               Int16GetDatum(estate->trigdata->tg_trigger->tgnargs),
+                               false, false);
+             break;
+
+         case PLPGSQL_PROMISE_TG_ARGV:
+             if (estate->trigdata == NULL)
+                 elog(ERROR, "trigger promise is not in a trigger function");
+             if (estate->trigdata->tg_trigger->tgnargs > 0)
+             {
+                 /*
+                  * For historical reasons, tg_argv[] subscripts start at zero
+                  * not one.  So we can't use construct_array().
+                  */
+                 int            nelems = estate->trigdata->tg_trigger->tgnargs;
+                 Datum       *elems;
+                 int            dims[1];
+                 int            lbs[1];
+                 int            i;
+
+                 elems = palloc(sizeof(Datum) * nelems);
+                 for (i = 0; i < nelems; i++)
+                     elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]);
+                 dims[0] = nelems;
+                 lbs[0] = 0;
+
+                 assign_simple_var(estate, var,
+                                   PointerGetDatum(construct_md_array(elems, NULL,
+                                                                      1, dims, lbs,
+                                                                      TEXTOID,
+                                                                      -1, false, 'i')),
+                                   false, true);
+             }
+             else
+             {
+                 assign_simple_var(estate, var, (Datum) 0, true, false);
+             }
+             break;
+
+         case PLPGSQL_PROMISE_TG_EVENT:
+             if (estate->evtrigdata == NULL)
+                 elog(ERROR, "event trigger promise is not in an event trigger function");
+             assign_text_var(estate, var, estate->evtrigdata->event);
+             break;
+
+         case PLPGSQL_PROMISE_TG_TAG:
+             if (estate->evtrigdata == NULL)
+                 elog(ERROR, "event trigger promise is not in an event trigger function");
+             assign_text_var(estate, var, estate->evtrigdata->tag);
+             break;
+
+         default:
+             elog(ERROR, "unrecognized promise type: %d", var->promise);
+     }
+
+     MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
   * Create a memory context for statement-lifespan variables, if we don't
   * have one already.  It will be a child of stmt_mcontext_parent, which is
   * either the function's main context or a pushed-down outer stmt_mcontext.
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1377,1382 ****
--- 1431,1440 ----

          /*
           * The set of dtypes handled here must match plpgsql_add_initdatums().
+          *
+          * Note that we currently don't support promise datums within blocks,
+          * only at a function's outermost scope, so we needn't handle those
+          * here.
           */
          switch (datum->dtype)
          {
*************** exec_stmt_return(PLpgSQL_execstate *esta
*** 2834,2839 ****
--- 2892,2903 ----

          switch (retvar->dtype)
          {
+             case PLPGSQL_DTYPE_PROMISE:
+                 /* fulfill promise if needed, then handle like regular var */
+                 plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
+
+                 /* FALL THRU */
+
              case PLPGSQL_DTYPE_VAR:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) retvar;
*************** exec_stmt_return_next(PLpgSQL_execstate
*** 2973,2978 ****
--- 3037,3048 ----

          switch (retvar->dtype)
          {
+             case PLPGSQL_DTYPE_PROMISE:
+                 /* fulfill promise if needed, then handle like regular var */
+                 plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
+
+                 /* FALL THRU */
+
              case PLPGSQL_DTYPE_VAR:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) retvar;
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3543,3548 ****
--- 3613,3620 ----
      func->cur_estate = estate;

      estate->func = func;
+     estate->trigdata = NULL;
+     estate->evtrigdata = NULL;

      estate->retval = (Datum) 0;
      estate->retisnull = true;
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4547,4552 ****
--- 4619,4625 ----
      switch (target->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  /*
                   * Target is a variable
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4609,4618 ****
--- 4682,4697 ----
                   * cannot reliably be made any earlier; we have to be looking
                   * at the object's standard R/W pointer to be sure pointer
                   * equality is meaningful.
+                  *
+                  * Also, if it's a promise variable, we should disarm the
+                  * promise in any case --- otherwise, assigning null to an
+                  * armed promise variable would fail to disarm the promise.
                   */
                  if (var->value != newvalue || var->isnull || isNull)
                      assign_simple_var(estate, var, newvalue, isNull,
                                        (!var->datatype->typbyval && !isNull));
+                 else
+                     var->promise = PLPGSQL_PROMISE_NONE;
                  break;
              }

*************** exec_eval_datum(PLpgSQL_execstate *estat
*** 4956,4961 ****
--- 5035,5046 ----

      switch (datum->dtype)
      {
+         case PLPGSQL_DTYPE_PROMISE:
+             /* fulfill promise if needed, then handle like regular var */
+             plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum);
+
+             /* FALL THRU */
+
          case PLPGSQL_DTYPE_VAR:
              {
                  PLpgSQL_var *var = (PLpgSQL_var *) datum;
*************** plpgsql_exec_get_datum_type(PLpgSQL_exec
*** 5098,5103 ****
--- 5183,5189 ----
      switch (datum->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  PLpgSQL_var *var = (PLpgSQL_var *) datum;

*************** plpgsql_exec_get_datum_type_info(PLpgSQL
*** 5181,5186 ****
--- 5267,5273 ----
      switch (datum->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  PLpgSQL_var *var = (PLpgSQL_var *) datum;

*************** plpgsql_param_fetch(ParamListInfo params
*** 5932,5937 ****
--- 6019,6025 ----
          switch (datum->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  /* always safe */
                  break;

*************** plpgsql_param_compile(ParamListInfo para
*** 6047,6054 ****
       * Select appropriate eval function.  It seems worth special-casing
       * DTYPE_VAR and DTYPE_RECFIELD for performance.  Also, we can determine
       * in advance whether MakeExpandedObjectReadOnly() will be required.
!      * Currently, only VAR and REC datums could contain read/write expanded
!      * objects.
       */
      if (datum->dtype == PLPGSQL_DTYPE_VAR)
      {
--- 6135,6142 ----
       * Select appropriate eval function.  It seems worth special-casing
       * DTYPE_VAR and DTYPE_RECFIELD for performance.  Also, we can determine
       * in advance whether MakeExpandedObjectReadOnly() will be required.
!      * Currently, only VAR/PROMISE and REC datums could contain read/write
!      * expanded objects.
       */
      if (datum->dtype == PLPGSQL_DTYPE_VAR)
      {
*************** plpgsql_param_compile(ParamListInfo para
*** 6060,6065 ****
--- 6148,6161 ----
      }
      else if (datum->dtype == PLPGSQL_DTYPE_RECFIELD)
          scratch.d.cparam.paramfunc = plpgsql_param_eval_recfield;
+     else if (datum->dtype == PLPGSQL_DTYPE_PROMISE)
+     {
+         if (dno != expr->rwparam &&
+             ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+             scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
+         else
+             scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
+     }
      else if (datum->dtype == PLPGSQL_DTYPE_REC &&
               dno != expr->rwparam)
          scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
*************** static void
*** 7740,7746 ****
  assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
                    Datum newvalue, bool isnull, bool freeable)
  {
!     Assert(var->dtype == PLPGSQL_DTYPE_VAR);
      /* Free the old value if needed */
      if (var->freeval)
      {
--- 7836,7843 ----
  assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
                    Datum newvalue, bool isnull, bool freeable)
  {
!     Assert(var->dtype == PLPGSQL_DTYPE_VAR ||
!            var->dtype == PLPGSQL_DTYPE_PROMISE);
      /* Free the old value if needed */
      if (var->freeval)
      {
*************** assign_simple_var(PLpgSQL_execstate *est
*** 7755,7760 ****
--- 7852,7864 ----
      var->value = newvalue;
      var->isnull = isnull;
      var->freeval = freeable;
+
+     /*
+      * If it's a promise variable, then either we just assigned the promised
+      * value, or the user explicitly assigned an overriding value.  Either
+      * way, cancel the promise.
+      */
+     var->promise = PLPGSQL_PROMISE_NONE;
  }

  /*
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
index f734af8..f499d96 100644
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
*************** plpgsql_free_function_memory(PLpgSQL_fun
*** 707,712 ****
--- 707,713 ----
          switch (d->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) d;

*************** plpgsql_dumptree(PLpgSQL_function *func)
*** 1538,1543 ****
--- 1539,1545 ----
          switch (d->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) d;

*************** plpgsql_dumptree(PLpgSQL_function *func)
*** 1564,1569 ****
--- 1566,1574 ----
                          dump_expr(var->cursor_explicit_expr);
                          printf("\n");
                      }
+                     if (var->promise != PLPGSQL_PROMISE_NONE)
+                         printf("                                  PROMISE %d\n",
+                                (int) var->promise);
                  }
                  break;
              case PLPGSQL_DTYPE_ROW:
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index df4694b..df88ba3 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** make_return_stmt(int location)
*** 3136,3141 ****
--- 3136,3142 ----

          if (tok == T_DATUM && plpgsql_peek() == ';' &&
              (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+              yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
               yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
               yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
          {
*************** make_return_next_stmt(int location)
*** 3197,3202 ****
--- 3198,3204 ----

          if (tok == T_DATUM && plpgsql_peek() == ';' &&
              (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+              yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
               yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
               yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
          {
*************** check_assignable(PLpgSQL_datum *datum, i
*** 3284,3289 ****
--- 3286,3292 ----
      switch (datum->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              if (((PLpgSQL_var *) datum)->isconst)
                  ereport(ERROR,
                          (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 01c14e3..96e92b2 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef enum PLpgSQL_datum_type
*** 63,72 ****
      PLPGSQL_DTYPE_ROW,
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
!     PLPGSQL_DTYPE_ARRAYELEM
  } PLpgSQL_datum_type;

  /*
   * Variants distinguished in PLpgSQL_type structs
   */
  typedef enum PLpgSQL_type_type
--- 63,92 ----
      PLPGSQL_DTYPE_ROW,
      PLPGSQL_DTYPE_REC,
      PLPGSQL_DTYPE_RECFIELD,
!     PLPGSQL_DTYPE_ARRAYELEM,
!     PLPGSQL_DTYPE_PROMISE
  } PLpgSQL_datum_type;

  /*
+  * DTYPE_PROMISE datums have these possible ways of computing the promise
+  */
+ typedef enum PLpgSQL_promise_type
+ {
+     PLPGSQL_PROMISE_NONE = 0,    /* not a promise, or promise satisfied */
+     PLPGSQL_PROMISE_TG_NAME,
+     PLPGSQL_PROMISE_TG_WHEN,
+     PLPGSQL_PROMISE_TG_LEVEL,
+     PLPGSQL_PROMISE_TG_OP,
+     PLPGSQL_PROMISE_TG_RELID,
+     PLPGSQL_PROMISE_TG_TABLE_NAME,
+     PLPGSQL_PROMISE_TG_TABLE_SCHEMA,
+     PLPGSQL_PROMISE_TG_NARGS,
+     PLPGSQL_PROMISE_TG_ARGV,
+     PLPGSQL_PROMISE_TG_EVENT,
+     PLPGSQL_PROMISE_TG_TAG
+ } PLpgSQL_promise_type;
+
+ /*
   * Variants distinguished in PLpgSQL_type structs
   */
  typedef enum PLpgSQL_type_type
*************** typedef struct PLpgSQL_variable
*** 246,251 ****
--- 266,279 ----

  /*
   * Scalar variable
+  *
+  * DTYPE_VAR and DTYPE_PROMISE datums both use this struct type.
+  * A PROMISE datum works exactly like a VAR datum for most purposes,
+  * but if it is read without having previously been assigned to, then
+  * a special "promised" value is computed and assigned to the datum
+  * before the read is performed.  This technique avoids the overhead of
+  * computing the variable's value in cases where we expect that many
+  * functions will never read it.
   */
  typedef struct PLpgSQL_var
  {
*************** typedef struct PLpgSQL_var
*** 269,277 ****
--- 297,314 ----
      int            cursor_explicit_argrow;
      int            cursor_options;

+     /* Fields below here can change at runtime */
+
      Datum        value;
      bool        isnull;
      bool        freeval;
+
+     /*
+      * The promise field records which "promised" value to assign if the
+      * promise must be honored.  If it's a normal variable, or the promise has
+      * been fulfilled, this is PLPGSQL_PROMISE_NONE.
+      */
+     PLpgSQL_promise_type promise;
  } PLpgSQL_var;

  /*
*************** typedef struct PLpgSQL_function
*** 849,868 ****
      int            found_varno;
      int            new_varno;
      int            old_varno;
-     int            tg_name_varno;
-     int            tg_when_varno;
-     int            tg_level_varno;
-     int            tg_op_varno;
-     int            tg_relid_varno;
-     int            tg_relname_varno;
-     int            tg_table_name_varno;
-     int            tg_table_schema_varno;
-     int            tg_nargs_varno;
-     int            tg_argv_varno;
-
-     /* for event triggers */
-     int            tg_event_varno;
-     int            tg_tag_varno;

      PLpgSQL_resolve_option resolve_option;

--- 886,891 ----
*************** typedef struct PLpgSQL_execstate
*** 892,897 ****
--- 915,923 ----
  {
      PLpgSQL_function *func;        /* function being executed */

+     TriggerData *trigdata;        /* if regular trigger, data about firing */
+     EventTriggerData *evtrigdata;    /* if event trigger, data about firing */
+
      Datum        retval;
      bool        retisnull;
      Oid            rettype;        /* type of current retval */

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Antonio Belloni
Дата:
Сообщение: Contributing with code
Следующее
От: Alvaro Herrera
Дата:
Сообщение: Re: pgsql: Get rid of copy_partition_key