Re: plpgsql function startup-time improvements - Mailing list pgsql-hackers

From Tom Lane
Subject Re: plpgsql function startup-time improvements
Date
Msg-id 8376.1516835784@sss.pgh.pa.us
Whole thread Raw
In response to Re: plpgsql function startup-time improvements  (Pavel Stehule <pavel.stehule@gmail.com>)
Responses Re: plpgsql function startup-time improvements
List pgsql-hackers
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 */

pgsql-hackers by date:

Previous
From: Alvaro Herrera
Date:
Subject: reducing isolation tests runtime
Next
From: Robert Haas
Date:
Subject: Re: reducing isolation tests runtime