Thread: plpgsql function startup-time improvements

plpgsql function startup-time improvements

From
Tom Lane
Date:
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 */

Re: plpgsql function startup-time improvements

From
Peter Eisentraut
Date:
On 12/27/17 15:38, Tom Lane wrote:
> 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.

This might be useful for instantiating virtual generated column values
in AFTER triggers on demand.  Although this would require promises on
record *fields*.  Anyway, it's useful infrastructure, and could have
more uses.

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services


Re: plpgsql function startup-time improvements

From
"Tels"
Date:
Hello Tom,

On Wed, December 27, 2017 3:38 pm, Tom Lane wrote:
> Attached are patches for two performance-improvement ideas that came
> to me while working on
[snip]
>
> 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.

More performance, especially in plpgsql is always a good thing :)

After a few experiments I've got a question or two, though (and please
excuse if this are stupid questions :)

My C is a bit rusty, so I embarked on a mission to learn more.

With a short test program printing out the size of PLpgSQL_var to check, I
always saw 72 bytes, regardless of bool vs. int. So a bool would be 4
bytes here. Hmm.

Googling around, this patch comment from Peter Eisentraut says that "bool"
can be more than one byte, and only "bool8" is really one byte.

https://www.postgresql.org/message-id/attachment/54267/0006-Add-bool8-typedef-for-system-catalog-structs.patch

I used 64-bit Kubuntu, the gcc that came with the system, and installed
Postgres 10 to see what MAXIMUM_ALIGNOF should be (it says 8 here).

Since I could get the test program to compile against all PG header files
(which is probably me being just being dense...), I used a stripped down
test.c (attached).

However, I probably did not have the real compiler settings, which might
influence the size of bool or the enum definition?

Maybe someone could shed some light on these questions:

* Does bool vs. int really save space or is this maybe system/compiler
dependend?

* Or should the structs use "bool8" to ensure this?

* I dimly remember that access to 1-byte fields might be slower than to 4
byte fields on X86-64. Would the size savings still worth it?

And maybe folding all four bool fields into an "int flags" field with bits
would save space, and not much slower (depending on often how the
different flags are accessed due to the ANDing and ORing ops)?

Best regards,

Tels
Attachment

Re: plpgsql function startup-time improvements

From
Tom Lane
Date:
"Tels" <nospam-pg-abuse@bloodgate.com> writes:
> On Wed, December 27, 2017 3:38 pm, Tom Lane wrote:
>> 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.

> With a short test program printing out the size of PLpgSQL_var to check, I
> always saw 72 bytes, regardless of bool vs. int. So a bool would be 4
> bytes here. Hmm.

Seems fairly unlikely, especially given that we typedef bool as char ;-).

Which field order were you checking?  Are you accounting for alignment
padding?

By my count, with the existing field order on typical 64-bit hardware,
we ought to have

    PLpgSQL_datum_type dtype;       -- 4 bytes [1]
    int         dno;                -- 4 bytes
    char       *refname;            -- 8 bytes
    int         lineno;             -- 4 bytes
                                    -- 4 bytes wasted due to padding here
    PLpgSQL_type *datatype;         -- 8 bytes
    int         isconst;            -- 4 bytes
    int         notnull;            -- 4 bytes
    PLpgSQL_expr *default_val;      -- 8 bytes
    PLpgSQL_expr *cursor_explicit_expr;     -- 8 bytes
    int         cursor_explicit_argrow;     -- 4 bytes
    int         cursor_options;             -- 4 bytes

    Datum       value;              -- 8 bytes
    bool        isnull;             -- 1 byte
    bool        freeval;            -- 1 byte

so at this point we've consumed 74 bytes, but the whole struct has
to be aligned on 8-byte boundaries because of the pointers, so
sizeof(PLpgSQL_var) ought to be 80 --- and that is what I see here.

With the proposed redesign,

    PLpgSQL_datum_type dtype;       -- 4 bytes [1]
    int         dno;                -- 4 bytes
    char       *refname;            -- 8 bytes
    int         lineno;             -- 4 bytes

    bool        isconst;            -- 1 byte
    bool        notnull;            -- 1 byte
                                    -- 2 bytes wasted due to padding here
    PLpgSQL_type *datatype;         -- 8 bytes
    PLpgSQL_expr *default_val;      -- 8 bytes
    PLpgSQL_expr *cursor_explicit_expr;     -- 8 bytes
    int         cursor_explicit_argrow;     -- 4 bytes
    int         cursor_options;             -- 4 bytes

    Datum       value;              -- 8 bytes
    bool        isnull;             -- 1 byte
    bool        freeval;            -- 1 byte

so we've consumed 66 bytes, which rounds up to 72 with the addition
of trailing padding.

> Googling around, this patch comment from Peter Eisentraut says that "bool"
> can be more than one byte, and only "bool8" is really one byte.

Even if you're allowing stdbool.h to determine sizeof(bool), I'm pretty
sure all Intel-based platforms define that to be 1.

> Since I could get the test program to compile against all PG header files
> (which is probably me being just being dense...), I used a stripped down
> test.c (attached).

Hm.  I wonder if your <stdbool.h> behaves differently from mine.
You might try printing out sizeof(bool) directly to see.

> And maybe folding all four bool fields into an "int flags" field with bits
> would save space, and not much slower (depending on often how the
> different flags are accessed due to the ANDing and ORing ops)?

That'd be pretty messy/invasive in terms of the code changes needed,
and I don't think it'd save any space once you account for alignment
and the fact that my other patch proposes to add another enum at
the end of the struct.  Also, I'm not exactly convinced that
replacing byte sets and tests with bitflag operations would be
cheap time-wise.  (It would particularly be a mess for isnull,
since then there'd be an impedance mismatch with a whole lotta PG
APIs that expect null flags to be bools.)

            regards, tom lane

[1] Actually, in principle that enum could be 1, 2, or 4 bytes depending
on compiler.  But the alignment requirement for dno would mean dtype plus
any padding after it would occupy 4 bytes no matter what.


Re: plpgsql function startup-time improvements

From
"Tels"
Date:
Moin,

On Thu, December 28, 2017 5:43 pm, Tom Lane wrote:
> "Tels" <nospam-pg-abuse@bloodgate.com> writes:
>> On Wed, December 27, 2017 3:38 pm, Tom Lane wrote:
>>> 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.
>
>> With a short test program printing out the size of PLpgSQL_var to check,
>> I
>> always saw 72 bytes, regardless of bool vs. int. So a bool would be 4
>> bytes here. Hmm.
>
> Seems fairly unlikely, especially given that we typedef bool as char ;-).

Hmn, yes, I can see my confusion. And a test shows, that sizeof(bool) is 1
here. and *char etc are 8.

> Which field order were you checking?  Are you accounting for alignment
> padding?
>
> By my count, with the existing field order on typical 64-bit hardware,
> we ought to have
>
>     PLpgSQL_datum_type dtype;       -- 4 bytes [1]
>     int         dno;                -- 4 bytes
>     char       *refname;            -- 8 bytes
>     int         lineno;             -- 4 bytes
>                                     -- 4 bytes wasted due to padding here
>     PLpgSQL_type *datatype;         -- 8 bytes
>     int         isconst;            -- 4 bytes
>     int         notnull;            -- 4 bytes
>     PLpgSQL_expr *default_val;      -- 8 bytes
>     PLpgSQL_expr *cursor_explicit_expr;     -- 8 bytes
>     int         cursor_explicit_argrow;     -- 4 bytes
>     int         cursor_options;             -- 4 bytes
>
>     Datum       value;              -- 8 bytes
>     bool        isnull;             -- 1 byte
>     bool        freeval;            -- 1 byte
>
> so at this point we've consumed 74 bytes, but the whole struct has
> to be aligned on 8-byte boundaries because of the pointers, so
> sizeof(PLpgSQL_var) ought to be 80 --- and that is what I see here.
>
> With the proposed redesign,
>
>     PLpgSQL_datum_type dtype;       -- 4 bytes [1]
>     int         dno;                -- 4 bytes
>     char       *refname;            -- 8 bytes
>     int         lineno;             -- 4 bytes
>
>     bool        isconst;            -- 1 byte
>     bool        notnull;            -- 1 byte
>                                     -- 2 bytes wasted due to padding here
>     PLpgSQL_type *datatype;         -- 8 bytes
>     PLpgSQL_expr *default_val;      -- 8 bytes
>     PLpgSQL_expr *cursor_explicit_expr;     -- 8 bytes
>     int         cursor_explicit_argrow;     -- 4 bytes
>     int         cursor_options;             -- 4 bytes
>
>     Datum       value;              -- 8 bytes
>     bool        isnull;             -- 1 byte
>     bool        freeval;            -- 1 byte
>
> so we've consumed 66 bytes, which rounds up to 72 with the addition
> of trailing padding.

Sounds logical, thanx for the detailed explanation. In my test the first 4
padding bytes are probably not there, because I probably miss something
that aligns ptrs on 8-byte boundaries. At least that would explain the
sizes seen here.

So, if you moved "isnull" and "freeval" right behind "isconst" and
"notnull", you could save another 2 byte, land at 64, and thus no extra
padding would keep it at 64?

>> And maybe folding all four bool fields into an "int flags" field with
>> bits
>> would save space, and not much slower (depending on often how the
>> different flags are accessed due to the ANDing and ORing ops)?
>
> That'd be pretty messy/invasive in terms of the code changes needed,
> and I don't think it'd save any space once you account for alignment
> and the fact that my other patch proposes to add another enum at
> the end of the struct.  Also, I'm not exactly convinced that
> replacing byte sets and tests with bitflag operations would be
> cheap time-wise.  (It would particularly be a mess for isnull,
> since then there'd be an impedance mismatch with a whole lotta PG
> APIs that expect null flags to be bools.)

Already had a hunch the idea wouldn't be popular, and this are all pretty
solid arguments against it. Nevermind, then :)

Best wishes,

Tels


Re: plpgsql function startup-time improvements

From
Tom Lane
Date:
"Tels" <nospam-pg-abuse@bloodgate.com> writes:
> On Thu, December 28, 2017 5:43 pm, Tom Lane wrote:
>> Which field order were you checking?  Are you accounting for alignment
>> padding?
> ...
> Sounds logical, thanx for the detailed explanation. In my test the first 4
> padding bytes are probably not there, because I probably miss something
> that aligns ptrs on 8-byte boundaries. At least that would explain the
> sizes seen here.

Hm, yeah, you would get different results on a compiler that considered
pointers to only require 4-byte alignment.  Although that would be an
ABI break, so I'm surprised to hear there are any x86_64 compilers that
do that by default.

> So, if you moved "isnull" and "freeval" right behind "isconst" and
> "notnull", you could save another 2 byte, land at 64, and thus no extra
> padding would keep it at 64?

I thought about that idea while reading your earlier message,
but I don't favor it for two reasons:

* It'd be an illogical ordering of the fields: isnull and freeval
belong with the value, not somewhere else.  This is not simply an
academic point, because of the field-sharing conventions represented
by PLpgSQL_variable.  I expect that sometime soon we will get
around to implementing CONSTANT, NOT NULL, and initializer properties
for composite variables, and the most natural way to do that will be
to include the isconst, notnull, and default_val fields into
PLpgSQL_variable so they can be shared by PLpgSQL_var and PLpgSQL_rec.
Shoving isnull and freeval into the middle of that would be really ugly.

* The second patch I posted in this thread adds another enum field
at the end of PLpgSQL_var.  Right now, with either field ordering
under discussion, that's basically free space-wise because it's
just going into what would otherwise be trailing padding space.
But it destroys any space savings from moving isnull and freeval
somewhere else.

In the end, it's not wise to put too much emphasis on struct size
and padding considerations, as that just ends up being platform
dependent anyway.  None of what we've said in this back-and-forth
is quite right for 32-bit-pointer machines, and once Peter's stdbool
patch lands, the assumption that bool is 1 byte will be shaky as
well.  I think actually the point about maintaining a logical field
order is the most significant consideration here.  There's no great
harm in trying to avoid space wastage on today's most popular
machines, but we shouldn't let that consideration contort the code
very far.

            regards, tom lane


Re: plpgsql function startup-time improvements

From
Pavel Stehule
Date:
Hi

2017-12-27 21:38 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:
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.

please, can you rebase all three patches necessary for patching?


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.

All mentioned ideas has sense.

Regards

Pavel


                        regards, tom lane


Re: plpgsql function startup-time improvements

From
Tom Lane
Date:
Pavel Stehule <pavel.stehule@gmail.com> writes:
> please, can you rebase all three patches necessary for patching?

Done.  These now need to be applied over
https://www.postgresql.org/message-id/833.1516834367@sss.pgh.pa.us

            regards, tom lane

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 09ecaec..97cb763 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** plpgsql_adddatum(PLpgSQL_datum *new)
*** 2183,2194 ****
--- 2183,2211 ----
  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 e119744..e2d315c 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static HTAB *shared_cast_hash = NULL;
*** 235,241 ****
  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);
--- 235,242 ----
  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 *
*** 458,465 ****
       * 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
--- 459,465 ----
       * 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
*** 859,866 ****
       * 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
--- 859,865 ----
       * 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
*** 1153,1159 ****
  {
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
-     int            i;
      int            rc;
      PLpgSQL_var *var;

--- 1152,1157 ----
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1174,1181 ****
       * 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
--- 1172,1178 ----
       * 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)
*** 1290,1346 ****
   * 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;
  }

  /*
--- 1287,1359 ----
   * 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 *
*** 3504,3511 ****

      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 */
--- 3517,3524 ----

      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 97c0d4f..ee943ee 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** decl_statement    : decl_varname decl_const
*** 564,570 ****

                          curname_def = palloc0(sizeof(PLpgSQL_expr));

-                         curname_def->dtype = PLPGSQL_DTYPE_EXPR;
                          strcpy(buf, "SELECT ");
                          cp1 = new->refname;
                          cp2 = buf + strlen(buf);
--- 564,569 ----
*************** read_sql_construct(int until,
*** 2697,2703 ****
      }

      expr = palloc0(sizeof(PLpgSQL_expr));
-     expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
      expr->paramnos        = NULL;
--- 2696,2701 ----
*************** make_execsql_stmt(int firsttoken, int lo
*** 2944,2950 ****
          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;
--- 2942,2947 ----
*************** read_cursor_args(PLpgSQL_var *cursor, in
*** 3816,3822 ****
      appendStringInfoChar(&ds, ';');

      expr = palloc0(sizeof(PLpgSQL_expr));
-     expr->dtype            = PLPGSQL_DTYPE_EXPR;
      expr->query            = pstrdup(ds.data);
      expr->plan            = NULL;
      expr->paramnos        = NULL;
--- 3813,3818 ----
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index d4eb67b..dadbfb5 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
*** 189,226 ****
  } 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 */
--- 188,197 ----
*************** typedef struct PLpgSQL_expr
*** 250,255 ****
--- 221,252 ----
  } 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
*** 258,268 ****
      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;
--- 255,272 ----
      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
*** 286,291 ****
--- 290,296 ----
      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
*** 308,313 ****
--- 313,320 ----
      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
*** 322,327 ****
--- 329,336 ----
  {
      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
*** 337,342 ****
--- 346,353 ----
  {
      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
*** 884,889 ****
--- 895,901 ----
      /* 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
*** 920,927 ****
      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) */
--- 932,945 ----
      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 97cb763..526aa8f 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** do_compile(FunctionCallInfo fcinfo,
*** 607,613 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_name_varno = var->dno;

              /* Add the variable tg_when */
              var = plpgsql_build_variable("tg_when", 0,
--- 607,615 ----
                                                                  -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,
*** 615,621 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_when_varno = var->dno;

              /* Add the variable tg_level */
              var = plpgsql_build_variable("tg_level", 0,
--- 617,625 ----
                                                                  -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,
*** 623,629 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_level_varno = var->dno;

              /* Add the variable tg_op */
              var = plpgsql_build_variable("tg_op", 0,
--- 627,635 ----
                                                                  -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,
*** 631,637 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_op_varno = var->dno;

              /* Add the variable tg_relid */
              var = plpgsql_build_variable("tg_relid", 0,
--- 637,645 ----
                                                                  -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,
*** 639,645 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_relid_varno = var->dno;

              /* Add the variable tg_relname */
              var = plpgsql_build_variable("tg_relname", 0,
--- 647,655 ----
                                                                  -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,
*** 647,653 ****
                                                                  -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,
--- 657,665 ----
                                                                  -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,
*** 655,661 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_table_name_varno = var->dno;

              /* add the variable tg_table_schema */
              var = plpgsql_build_variable("tg_table_schema", 0,
--- 667,675 ----
                                                                  -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,
*** 663,669 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_table_schema_varno = var->dno;

              /* Add the variable tg_nargs */
              var = plpgsql_build_variable("tg_nargs", 0,
--- 677,685 ----
                                                                  -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,
*** 671,677 ****
                                                                  -1,
                                                                  InvalidOid),
                                           true);
!             function->tg_nargs_varno = var->dno;

              /* Add the variable tg_argv */
              var = plpgsql_build_variable("tg_argv", 0,
--- 687,695 ----
                                                                  -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,
*** 679,685 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_argv_varno = var->dno;

              break;

--- 697,705 ----
                                                                  -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,
*** 701,707 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_event_varno = var->dno;

              /* Add the variable tg_tag */
              var = plpgsql_build_variable("tg_tag", 0,
--- 721,729 ----
                                                                  -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,
*** 709,715 ****
                                                                  -1,
                                                                  function->fn_input_collation),
                                           true);
!             function->tg_tag_varno = var->dno;

              break;

--- 731,739 ----
                                                                  -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
*** 1878,1883 ****
--- 1902,1908 ----
          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 *
*** 2196,2201 ****
--- 2221,2227 ----
          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 e2d315c..d578c56 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static void coerce_function_result_tuple
*** 237,242 ****
--- 237,244 ----
  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 *
*** 547,552 ****
--- 549,555 ----
                  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
*** 834,843 ****
  {
      PLpgSQL_execstate estate;
      ErrorContextCallback plerrcontext;
-     int            i;
      int            rc;
      TupleDesc    tupdesc;
-     PLpgSQL_var *var;
      PLpgSQL_rec *rec_new,
                 *rec_old;
      HeapTuple    rettup;
--- 837,844 ----
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 846,851 ****
--- 847,853 ----
       * 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
*** 906,1011 ****
      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");

      /*
--- 908,913 ----
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 1153,1164 ****
      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()
--- 1055,1066 ----
      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
*** 1175,1189 ****
      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)
--- 1077,1082 ----
*************** copy_plpgsql_datums(PLpgSQL_execstate *e
*** 1321,1326 ****
--- 1214,1220 ----
          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
*** 1357,1362 ****
--- 1251,1416 ----
  }

  /*
+  * 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
*** 1464,1469 ****
--- 1518,1527 ----

          /*
           * 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
*** 2778,2783 ****
--- 2836,2847 ----

          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
*** 2917,2922 ****
--- 2981,2992 ----

          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 *
*** 3487,3492 ****
--- 3557,3564 ----
      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
*** 4542,4547 ****
--- 4614,4620 ----
      switch (target->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  /*
                   * Target is a variable
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4604,4613 ****
--- 4677,4692 ----
                   * 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
*** 4951,4956 ****
--- 5030,5041 ----

      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
*** 5093,5098 ****
--- 5178,5184 ----
      switch (datum->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  PLpgSQL_var *var = (PLpgSQL_var *) datum;

*************** plpgsql_exec_get_datum_type_info(PLpgSQL
*** 5176,5181 ****
--- 5262,5268 ----
      switch (datum->dtype)
      {
          case PLPGSQL_DTYPE_VAR:
+         case PLPGSQL_DTYPE_PROMISE:
              {
                  PLpgSQL_var *var = (PLpgSQL_var *) datum;

*************** plpgsql_param_fetch(ParamListInfo params
*** 5874,5879 ****
--- 5961,5967 ----
          switch (datum->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  /* always safe */
                  break;

*************** plpgsql_param_compile(ParamListInfo para
*** 5989,5996 ****
       * 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)
      {
--- 6077,6084 ----
       * 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
*** 6002,6007 ****
--- 6090,6103 ----
      }
      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
*** 7680,7686 ****
  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)
      {
--- 7776,7783 ----
  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
*** 7695,7700 ****
--- 7792,7804 ----
      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 b36fab6..379fd69 100644
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
*************** plpgsql_free_function_memory(PLpgSQL_fun
*** 729,734 ****
--- 729,735 ----
          switch (d->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) d;

*************** plpgsql_dumptree(PLpgSQL_function *func)
*** 1582,1587 ****
--- 1583,1589 ----
          switch (d->dtype)
          {
              case PLPGSQL_DTYPE_VAR:
+             case PLPGSQL_DTYPE_PROMISE:
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) d;

*************** plpgsql_dumptree(PLpgSQL_function *func)
*** 1608,1613 ****
--- 1610,1618 ----
                          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 ee943ee..5bf4594 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** make_return_stmt(int location)
*** 3170,3175 ****
--- 3170,3176 ----

          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)
*** 3231,3236 ****
--- 3232,3238 ----

          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
*** 3318,3323 ****
--- 3320,3326 ----
      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 dadbfb5..01b89a5 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
*** 248,253 ****
--- 268,281 ----

  /*
   * 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
*** 271,279 ****
--- 299,316 ----
      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
*** 869,888 ****
      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;

--- 906,911 ----
*************** typedef struct PLpgSQL_execstate
*** 912,917 ****
--- 935,943 ----
  {
      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 */

Re: plpgsql function startup-time improvements

From
Pavel Stehule
Date:
Hi

2018-01-25 0:16 GMT+01:00 Tom Lane <tgl@sss.pgh.pa.us>:
Pavel Stehule <pavel.stehule@gmail.com> writes:
> please, can you rebase all three patches necessary for patching?

Done.  These now need to be applied over
https://www.postgresql.org/message-id/833.1516834367@sss.pgh.pa.us

Thank you

I checked it

1. there are no problem with patching
2. all tests passed
3. I can confirm so some best case speedup is about 15-20%. Worst case is hard to test - but these changes should not be slower than current master.
4. faster-plpgsql-datum-setup-2.patch is simple patch with few lines of new code
5. plpgsql-promises-2.patch is little bit more complex, but still it is simple
6. the code is well formatted and well commented
7. there are not new test and code, what is not problem - these patches doesn't add any new feature

I'll mark these patches as ready for commiter

Regards

Pavel

 


                        regards, tom lane