Re: Faster setup_param_list() in plpgsql

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Re: Faster setup_param_list() in plpgsql
Дата
Msg-id 10845.1435866244@sss.pgh.pa.us
обсуждение исходный текст
Ответ на Faster setup_param_list() in plpgsql  (Tom Lane <tgl@sss.pgh.pa.us>)
Список pgsql-hackers
I wrote:
> What this patch does is to remove setup_param_list() overhead for the
> common case of PLPGSQL_DTYPE_VAR variables (ie, any non-composite type).
> It does that by the expedient of keeping the ParamExternData image of such
> a variable valid at all times.  That adds a few cycles to assignments to
> these variables, but removes more cycles from each use of them.  Unless
> you believe that common plpgsql functions contain lots of dead stores,
> this is a guaranteed win overall.

> I'm seeing about 10% overall speedup (vs HEAD, with casserts off) for
> realistic simple plpgsql logic, such as this test case:

Here is a version that is rebased up to HEAD.  Dunno if anyone wants
to re-review this, if not I'll go commit it.

            regards, tom lane

diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 0ff2086..05268e3 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
*************** PLpgSQL_stmt_block *plpgsql_parse_result
*** 42,48 ****
  static int    datums_alloc;
  int            plpgsql_nDatums;
  PLpgSQL_datum **plpgsql_Datums;
! static int    datums_last = 0;

  char       *plpgsql_error_funcname;
  bool        plpgsql_DumpExecTree = false;
--- 42,48 ----
  static int    datums_alloc;
  int            plpgsql_nDatums;
  PLpgSQL_datum **plpgsql_Datums;
! static int    datums_last;

  char       *plpgsql_error_funcname;
  bool        plpgsql_DumpExecTree = false;
*************** static Node *make_datum_param(PLpgSQL_ex
*** 104,109 ****
--- 104,111 ----
  static PLpgSQL_row *build_row_from_class(Oid classOid);
  static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
  static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod, Oid collation);
+ static void plpgsql_start_datums(void);
+ static void plpgsql_finish_datums(PLpgSQL_function *function);
  static void compute_function_hashkey(FunctionCallInfo fcinfo,
                           Form_pg_proc procStruct,
                           PLpgSQL_func_hashkey *hashkey,
*************** do_compile(FunctionCallInfo fcinfo,
*** 371,383 ****
      plpgsql_ns_init();
      plpgsql_ns_push(NameStr(procStruct->proname));
      plpgsql_DumpExecTree = false;
!
!     datums_alloc = 128;
!     plpgsql_nDatums = 0;
!     /* This is short-lived, so needn't allocate in function's cxt */
!     plpgsql_Datums = MemoryContextAlloc(compile_tmp_cxt,
!                                      sizeof(PLpgSQL_datum *) * datums_alloc);
!     datums_last = 0;

      switch (function->fn_is_trigger)
      {
--- 373,379 ----
      plpgsql_ns_init();
      plpgsql_ns_push(NameStr(procStruct->proname));
      plpgsql_DumpExecTree = false;
!     plpgsql_start_datums();

      switch (function->fn_is_trigger)
      {
*************** do_compile(FunctionCallInfo fcinfo,
*** 758,767 ****
      function->fn_nargs = procStruct->pronargs;
      for (i = 0; i < function->fn_nargs; i++)
          function->fn_argvarnos[i] = in_arg_varnos[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];

      /* Debug dump for completed functions */
      if (plpgsql_DumpExecTree)
--- 754,761 ----
      function->fn_nargs = procStruct->pronargs;
      for (i = 0; i < function->fn_nargs; i++)
          function->fn_argvarnos[i] = in_arg_varnos[i];
!
!     plpgsql_finish_datums(function);

      /* Debug dump for completed functions */
      if (plpgsql_DumpExecTree)
*************** plpgsql_compile_inline(char *proc_source
*** 804,810 ****
      PLpgSQL_variable *var;
      int            parse_rc;
      MemoryContext func_cxt;
-     int            i;

      /*
       * Setup the scanner input and error info.  We assume that this function
--- 798,803 ----
*************** plpgsql_compile_inline(char *proc_source
*** 860,870 ****
      plpgsql_ns_init();
      plpgsql_ns_push(func_name);
      plpgsql_DumpExecTree = false;
!
!     datums_alloc = 128;
!     plpgsql_nDatums = 0;
!     plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
!     datums_last = 0;

      /* Set up as though in a function returning VOID */
      function->fn_rettype = VOIDOID;
--- 853,859 ----
      plpgsql_ns_init();
      plpgsql_ns_push(func_name);
      plpgsql_DumpExecTree = false;
!     plpgsql_start_datums();

      /* Set up as though in a function returning VOID */
      function->fn_rettype = VOIDOID;
*************** plpgsql_compile_inline(char *proc_source
*** 911,920 ****
       * Complete the function's info
       */
      function->fn_nargs = 0;
!     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];

      /*
       * Pop the error context stack
--- 900,907 ----
       * Complete the function's info
       */
      function->fn_nargs = 0;
!
!     plpgsql_finish_datums(function);

      /*
       * Pop the error context stack
*************** plpgsql_build_record(const char *refname
*** 1965,1970 ****
--- 1952,1958 ----
      rec->tup = NULL;
      rec->tupdesc = NULL;
      rec->freetup = false;
+     rec->freetupdesc = false;
      plpgsql_adddatum((PLpgSQL_datum *) rec);
      if (add2namespace)
          plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname);
*************** plpgsql_parse_err_condition(char *condna
*** 2312,2317 ****
--- 2300,2321 ----
  }

  /* ----------
+  * plpgsql_start_datums            Initialize datum list at compile startup.
+  * ----------
+  */
+ static void
+ plpgsql_start_datums(void)
+ {
+     datums_alloc = 128;
+     plpgsql_nDatums = 0;
+     /* This is short-lived, so needn't allocate in function's cxt */
+     plpgsql_Datums = MemoryContextAlloc(compile_tmp_cxt,
+                                      sizeof(PLpgSQL_datum *) * datums_alloc);
+     /* datums_last tracks what's been seen by plpgsql_add_initdatums() */
+     datums_last = 0;
+ }
+
+ /* ----------
   * plpgsql_adddatum            Add a variable, record or row
   *                    to the compiler's datum list.
   * ----------
*************** plpgsql_adddatum(PLpgSQL_datum *new)
*** 2329,2334 ****
--- 2333,2371 ----
      plpgsql_Datums[plpgsql_nDatums++] = new;
  }

+ /* ----------
+  * plpgsql_finish_datums    Copy completed datum info into function struct.
+  *
+  * This is also responsible for building resettable_datums, a bitmapset
+  * of the dnos of all ROW, REC, and RECFIELD datums in the function.
+  * ----------
+  */
+ static void
+ plpgsql_finish_datums(PLpgSQL_function *function)
+ {
+     Bitmapset  *resettable_datums = NULL;
+     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];
+         switch (function->datums[i]->dtype)
+         {
+             case PLPGSQL_DTYPE_ROW:
+             case PLPGSQL_DTYPE_REC:
+             case PLPGSQL_DTYPE_RECFIELD:
+                 resettable_datums = bms_add_member(resettable_datums, i);
+                 break;
+
+             default:
+                 break;
+         }
+     }
+     function->resettable_datums = resettable_datums;
+ }
+

  /* ----------
   * plpgsql_add_initdatums        Make an array of the datum numbers of
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 79dd6a2..7d4001c 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** static int exec_for_query(PLpgSQL_execst
*** 216,221 ****
--- 216,223 ----
                 Portal portal, bool prefetch_ok);
  static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
                   PLpgSQL_expr *expr);
+ static ParamListInfo setup_unshared_param_list(PLpgSQL_execstate *estate,
+                           PLpgSQL_expr *expr);
  static void plpgsql_param_fetch(ParamListInfo params, int paramid);
  static void exec_move_row(PLpgSQL_execstate *estate,
                PLpgSQL_rec *rec,
*************** static void exec_init_tuple_store(PLpgSQ
*** 242,249 ****
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
! static void free_var(PLpgSQL_var *var);
! static void assign_text_var(PLpgSQL_var *var, const char *str);
  static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
                         List *params);
  static void free_params_data(PreparedParamsData *ppd);
--- 244,253 ----
  static void exec_set_found(PLpgSQL_execstate *estate, bool state);
  static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
  static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
! static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
!                   Datum newvalue, bool isnull, bool freeable);
! static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
!                 const char *str);
  static PreparedParamsData *exec_eval_using_params(PLpgSQL_execstate *estate,
                         List *params);
  static void free_params_data(PreparedParamsData *ppd);
*************** plpgsql_exec_function(PLpgSQL_function *
*** 312,320 ****
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n];

!                     var->value = fcinfo->arg[i];
!                     var->isnull = fcinfo->argnull[i];
!                     var->freeval = false;

                      /*
                       * Force any array-valued parameter to be stored in
--- 316,325 ----
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n];

!                     assign_simple_var(&estate, var,
!                                       fcinfo->arg[i],
!                                       fcinfo->argnull[i],
!                                       false);

                      /*
                       * Force any array-valued parameter to be stored in
*************** plpgsql_exec_function(PLpgSQL_function *
*** 336,344 ****
                          if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
                          {
                              /* take ownership of R/W object */
!                             var->value = TransferExpandedObject(var->value,
!                                                        CurrentMemoryContext);
!                             var->freeval = true;
                          }
                          else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value)))
                          {
--- 341,351 ----
                          if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
                          {
                              /* take ownership of R/W object */
!                             assign_simple_var(&estate, var,
!                                            TransferExpandedObject(var->value,
!                                                        CurrentMemoryContext),
!                                               false,
!                                               true);
                          }
                          else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value)))
                          {
*************** plpgsql_exec_function(PLpgSQL_function *
*** 347,356 ****
                          else
                          {
                              /* flat array, so force to expanded form */
!                             var->value = expand_array(var->value,
!                                                       CurrentMemoryContext,
!                                                       NULL);
!                             var->freeval = true;
                          }
                      }
                  }
--- 354,365 ----
                          else
                          {
                              /* flat array, so force to expanded form */
!                             assign_simple_var(&estate, var,
!                                               expand_array(var->value,
!                                                         CurrentMemoryContext,
!                                                            NULL),
!                                               false,
!                                               true);
                          }
                      }
                  }
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 641,716 ****

      var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
      if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
!         var->value = CStringGetTextDatum("INSERT");
      else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
!         var->value = CStringGetTextDatum("UPDATE");
      else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
!         var->value = CStringGetTextDatum("DELETE");
      else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
!         var->value = CStringGetTextDatum("TRUNCATE");
      else
          elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
-     var->isnull = false;
-     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
!     var->value = DirectFunctionCall1(namein,
!                               CStringGetDatum(trigdata->tg_trigger->tgname));
!     var->isnull = false;
!     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
      if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
!         var->value = CStringGetTextDatum("BEFORE");
      else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
!         var->value = CStringGetTextDatum("AFTER");
      else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
!         var->value = CStringGetTextDatum("INSTEAD OF");
      else
          elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
-     var->isnull = false;
-     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
      if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
!         var->value = CStringGetTextDatum("ROW");
      else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
!         var->value = CStringGetTextDatum("STATEMENT");
      else
          elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
-     var->isnull = false;
-     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
!     var->value = ObjectIdGetDatum(trigdata->tg_relation->rd_id);
!     var->isnull = false;
!     var->freeval = false;

      var = (PLpgSQL_var *) (estate.datums[func->tg_relname_varno]);
!     var->value = DirectFunctionCall1(namein,
!             CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
!     var->isnull = false;
!     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_table_name_varno]);
!     var->value = DirectFunctionCall1(namein,
!             CStringGetDatum(RelationGetRelationName(trigdata->tg_relation)));
!     var->isnull = false;
!     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_table_schema_varno]);
!     var->value = DirectFunctionCall1(namein,
!                                      CStringGetDatum(
!                                                      get_namespace_name(
                                                          RelationGetNamespace(
!                                                    trigdata->tg_relation))));
!     var->isnull = false;
!     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_nargs_varno]);
!     var->value = Int16GetDatum(trigdata->tg_trigger->tgnargs);
!     var->isnull = false;
!     var->freeval = false;

      var = (PLpgSQL_var *) (estate.datums[func->tg_argv_varno]);
      if (trigdata->tg_trigger->tgnargs > 0)
--- 650,718 ----

      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)
*************** plpgsql_exec_trigger(PLpgSQL_function *f
*** 730,747 ****
          dims[0] = nelems;
          lbs[0] = 0;

!         var->value = PointerGetDatum(construct_md_array(elems, NULL,
!                                                         1, dims, lbs,
!                                                         TEXTOID,
!                                                         -1, false, 'i'));
!         var->isnull = false;
!         var->freeval = true;
      }
      else
      {
!         var->value = (Datum) 0;
!         var->isnull = true;
!         var->freeval = false;
      }

      estate.err_text = gettext_noop("during function entry");
--- 732,747 ----
          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");
*************** plpgsql_exec_event_trigger(PLpgSQL_funct
*** 874,887 ****
       * Assign the special tg_ variables
       */
      var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]);
!     var->value = CStringGetTextDatum(trigdata->event);
!     var->isnull = false;
!     var->freeval = true;

      var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
!     var->value = CStringGetTextDatum(trigdata->tag);
!     var->isnull = false;
!     var->freeval = true;

      /*
       * Let the instrumentation plugin peek at this function
--- 874,883 ----
       * 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
*************** copy_plpgsql_datum(PLpgSQL_datum *datum)
*** 1012,1021 ****
                  PLpgSQL_var *new = palloc(sizeof(PLpgSQL_var));

                  memcpy(new, datum, sizeof(PLpgSQL_var));
!                 /* Ensure the value is null (possibly not needed?) */
!                 new->value = 0;
!                 new->isnull = true;
!                 new->freeval = false;

                  result = (PLpgSQL_datum *) new;
              }
--- 1008,1016 ----
                  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;
              }
*************** copy_plpgsql_datum(PLpgSQL_datum *datum)
*** 1026,1036 ****
                  PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));

                  memcpy(new, datum, sizeof(PLpgSQL_rec));
!                 /* Ensure the value is null (possibly not needed?) */
!                 new->tup = NULL;
!                 new->tupdesc = NULL;
!                 new->freetup = false;
!                 new->freetupdesc = false;

                  result = (PLpgSQL_datum *) new;
              }
--- 1021,1031 ----
                  PLpgSQL_rec *new = palloc(sizeof(PLpgSQL_rec));

                  memcpy(new, datum, sizeof(PLpgSQL_rec));
!                 /* should be preset to null/non-freeable */
!                 Assert(new->tup == NULL);
!                 Assert(new->tupdesc == NULL);
!                 Assert(!new->freetup);
!                 Assert(!new->freetupdesc);

                  result = (PLpgSQL_datum *) new;
              }
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1114,1125 ****
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);

!                     /* free any old value, in case re-entering block */
!                     free_var(var);
!
!                     /* Initially it contains a NULL */
!                     var->value = (Datum) 0;
!                     var->isnull = true;

                      if (var->default_val == NULL)
                      {
--- 1109,1119 ----
                  {
                      PLpgSQL_var *var = (PLpgSQL_var *) (estate->datums[n]);

!                     /*
!                      * Free any old value, in case re-entering block, and
!                      * initialize to NULL
!                      */
!                     assign_simple_var(estate, var, (Datum) 0, true, false);

                      if (var->default_val == NULL)
                      {
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1308,1316 ****
                      errm_var = (PLpgSQL_var *)
                          estate->datums[block->exceptions->sqlerrm_varno];

!                     assign_text_var(state_var,
                                      unpack_sql_state(edata->sqlerrcode));
!                     assign_text_var(errm_var, edata->message);

                      /*
                       * Also set up cur_error so the error data is accessible
--- 1302,1310 ----
                      errm_var = (PLpgSQL_var *)
                          estate->datums[block->exceptions->sqlerrm_varno];

!                     assign_text_var(estate, state_var,
                                      unpack_sql_state(edata->sqlerrcode));
!                     assign_text_var(estate, errm_var, edata->message);

                      /*
                       * Also set up cur_error so the error data is accessible
*************** exec_stmt_case(PLpgSQL_execstate *estate
*** 1788,1798 ****

              /* We can now discard any value we had for the temp variable */
              if (t_var != NULL)
!             {
!                 free_var(t_var);
!                 t_var->value = (Datum) 0;
!                 t_var->isnull = true;
!             }

              /* Evaluate the statement(s), and we're done */
              return exec_stmts(estate, cwt->stmts);
--- 1782,1788 ----

              /* We can now discard any value we had for the temp variable */
              if (t_var != NULL)
!                 assign_simple_var(estate, t_var, (Datum) 0, true, false);

              /* Evaluate the statement(s), and we're done */
              return exec_stmts(estate, cwt->stmts);
*************** exec_stmt_case(PLpgSQL_execstate *estate
*** 1801,1811 ****

      /* We can now discard any value we had for the temp variable */
      if (t_var != NULL)
!     {
!         free_var(t_var);
!         t_var->value = (Datum) 0;
!         t_var->isnull = true;
!     }

      /* SQL2003 mandates this error if there was no ELSE clause */
      if (!stmt->have_else)
--- 1791,1797 ----

      /* We can now discard any value we had for the temp variable */
      if (t_var != NULL)
!         assign_simple_var(estate, t_var, (Datum) 0, true, false);

      /* SQL2003 mandates this error if there was no ELSE clause */
      if (!stmt->have_else)
*************** exec_stmt_fori(PLpgSQL_execstate *estate
*** 2035,2042 ****
          /*
           * Assign current value to loop var
           */
!         var->value = Int32GetDatum(loop_value);
!         var->isnull = false;

          /*
           * Execute the statements
--- 2021,2027 ----
          /*
           * Assign current value to loop var
           */
!         assign_simple_var(estate, var, Int32GetDatum(loop_value), false, false);

          /*
           * Execute the statements
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2228,2236 ****
          exec_prepare_plan(estate, query, curvar->cursor_options);

      /*
!      * Set up ParamListInfo (hook function and possibly data values)
       */
!     paramLI = setup_param_list(estate, query);

      /*
       * Open the cursor (the paramlist will get copied into the portal)
--- 2213,2221 ----
          exec_prepare_plan(estate, query, curvar->cursor_options);

      /*
!      * Set up short-lived ParamListInfo
       */
!     paramLI = setup_unshared_param_list(estate, query);

      /*
       * Open the cursor (the paramlist will get copied into the portal)
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2242,2252 ****
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));

      /*
       * If cursor variable was NULL, store the generated portal name in it
       */
      if (curname == NULL)
!         assign_text_var(curvar, portal->name);

      /*
       * Execute the loop.  We can't prefetch because the cursor is accessible
--- 2227,2241 ----
          elog(ERROR, "could not open cursor: %s",
               SPI_result_code_string(SPI_result));

+     /* don't need paramlist any more */
+     if (paramLI)
+         pfree(paramLI);
+
      /*
       * If cursor variable was NULL, store the generated portal name in it
       */
      if (curname == NULL)
!         assign_text_var(estate, curvar, portal->name);

      /*
       * Execute the loop.  We can't prefetch because the cursor is accessible
*************** exec_stmt_forc(PLpgSQL_execstate *estate
*** 2261,2271 ****
      SPI_cursor_close(portal);

      if (curname == NULL)
!     {
!         free_var(curvar);
!         curvar->value = (Datum) 0;
!         curvar->isnull = true;
!     }

      if (curname)
          pfree(curname);
--- 2250,2256 ----
      SPI_cursor_close(portal);

      if (curname == NULL)
!         assign_simple_var(estate, curvar, (Datum) 0, true, false);

      if (curname)
          pfree(curname);
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 3314,3319 ****
--- 3299,3305 ----
      estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
      estate->paramLI->parserSetupArg = NULL;        /* filled during use */
      estate->paramLI->numParams = estate->ndatums;
+     estate->params_dirty = false;

      /* set up for use of appropriate simple-expression EState */
      if (simple_eval_estate)
*************** exec_stmt_execsql(PLpgSQL_execstate *est
*** 3478,3484 ****
      }

      /*
!      * Set up ParamListInfo (hook function and possibly data values)
       */
      paramLI = setup_param_list(estate, expr);

--- 3464,3470 ----
      }

      /*
!      * Set up ParamListInfo to pass to executor
       */
      paramLI = setup_param_list(estate, expr);

*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3937,3943 ****
           * If cursor variable was NULL, store the generated portal name in it
           */
          if (curname == NULL)
!             assign_text_var(curvar, portal->name);

          return PLPGSQL_RC_OK;
      }
--- 3923,3929 ----
           * If cursor variable was NULL, store the generated portal name in it
           */
          if (curname == NULL)
!             assign_text_var(estate, curvar, portal->name);

          return PLPGSQL_RC_OK;
      }
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 3991,3999 ****
      }

      /*
!      * Set up ParamListInfo (hook function and possibly data values)
       */
!     paramLI = setup_param_list(estate, query);

      /*
       * Open the cursor
--- 3977,3985 ----
      }

      /*
!      * Set up short-lived ParamListInfo
       */
!     paramLI = setup_unshared_param_list(estate, query);

      /*
       * Open the cursor
*************** exec_stmt_open(PLpgSQL_execstate *estate
*** 4009,4018 ****
       * If cursor variable was NULL, store the generated portal name in it
       */
      if (curname == NULL)
!         assign_text_var(curvar, portal->name);

      if (curname)
          pfree(curname);

      return PLPGSQL_RC_OK;
  }
--- 3995,4006 ----
       * If cursor variable was NULL, store the generated portal name in it
       */
      if (curname == NULL)
!         assign_text_var(estate, curvar, portal->name);

      if (curname)
          pfree(curname);
+     if (paramLI)
+         pfree(paramLI);

      return PLPGSQL_RC_OK;
  }
*************** exec_assign_value(PLpgSQL_execstate *est
*** 4282,4300 ****
                  }

                  /*
!                  * Now free the old value, unless it's the same as the new
!                  * value (ie, we're doing "foo := foo").  Note that for
!                  * expanded objects, this test is necessary and 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.
                   */
                  if (var->value != newvalue || var->isnull || isNull)
!                     free_var(var);
!
!                 var->value = newvalue;
!                 var->isnull = isNull;
!                 var->freeval = (!var->datatype->typbyval && !isNull);
                  break;
              }

--- 4270,4285 ----
                  }

                  /*
!                  * Now free the old value, if any, and assign the new one. But
!                  * skip the assignment if old and new values are the same.
!                  * Note that for expanded objects, this test is necessary and
!                  * 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.
                   */
                  if (var->value != newvalue || var->isnull || isNull)
!                     assign_simple_var(estate, var, newvalue, isNull,
!                                       (!var->datatype->typbyval && !isNull));
                  break;
              }

*************** exec_assign_value(PLpgSQL_execstate *est
*** 4638,4644 ****
   *
   * The type oid, typmod, value in Datum format, and null flag are returned.
   *
!  * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums.
   *
   * NOTE: the returned Datum points right at the stored value in the case of
   * pass-by-reference datatypes.  Generally callers should take care not to
--- 4623,4630 ----
   *
   * The type oid, typmod, value in Datum format, and null flag are returned.
   *
!  * At present this doesn't handle PLpgSQL_expr or PLpgSQL_arrayelem datums;
!  * that's not needed because we never pass references to such datums to SPI.
   *
   * NOTE: the returned Datum points right at the stored value in the case of
   * pass-by-reference datatypes.  Generally callers should take care not to
*************** exec_run_select(PLpgSQL_execstate *estat
*** 5082,5106 ****
          exec_prepare_plan(estate, expr, 0);

      /*
-      * Set up ParamListInfo (hook function and possibly data values)
-      */
-     paramLI = setup_param_list(estate, expr);
-
-     /*
       * If a portal was requested, put the query into the portal
       */
      if (portalP != NULL)
      {
          *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
                                                    paramLI,
                                                    estate->readonly_func);
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
          return SPI_OK_CURSOR;
      }

      /*
       * Execute the query
       */
      rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
--- 5068,5099 ----
          exec_prepare_plan(estate, expr, 0);

      /*
       * If a portal was requested, put the query into the portal
       */
      if (portalP != NULL)
      {
+         /*
+          * Set up short-lived ParamListInfo
+          */
+         paramLI = setup_unshared_param_list(estate, expr);
+
          *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
                                                    paramLI,
                                                    estate->readonly_func);
          if (*portalP == NULL)
              elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
                   expr->query, SPI_result_code_string(SPI_result));
+         if (paramLI)
+             pfree(paramLI);
          return SPI_OK_CURSOR;
      }

      /*
+      * Set up ParamListInfo to pass to executor
+      */
+     paramLI = setup_param_list(estate, expr);
+
+     /*
       * Execute the query
       */
      rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5402,5408 ****
      }

      /*
!      * Set up param list.  For safety, save and restore
       * estate->paramLI->parserSetupArg around our use of the param list.
       */
      save_setup_arg = estate->paramLI->parserSetupArg;
--- 5395,5401 ----
      }

      /*
!      * Set up ParamListInfo to pass to executor.  For safety, save and restore
       * estate->paramLI->parserSetupArg around our use of the param list.
       */
      save_setup_arg = estate->paramLI->parserSetupArg;
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 5451,5473 ****
   * Create a ParamListInfo to pass to SPI
   *
   * We share a single ParamListInfo array across all SPI calls made from this
!  * estate.  This is generally OK since any given slot in the array would
!  * need to contain the same current datum value no matter which query or
!  * expression we're evaluating.  However, paramLI->parserSetupArg points to
!  * the specific PLpgSQL_expr being evaluated.  This is not an issue for
!  * statement-level callers, but lower-level callers should save and restore
!  * estate->paramLI->parserSetupArg just in case there's an active evaluation
!  * at an outer call level.
   *
!  * We fill in the values for any expression parameters that are plain
!  * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting
!  * them with PARAM_FLAG_CONST flags, we allow the planner to use those values
!  * in custom plans.  However, parameters that are not plain PLpgSQL_vars
!  * should not be evaluated here, because they could throw errors (for example
!  * "no such record field") and we do not want that to happen in a part of
!  * the expression that might never be evaluated at runtime.  To handle those
!  * parameters, we set up a paramFetch hook for the executor to call when it
!  * wants a not-presupplied value.
   */
  static ParamListInfo
  setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
--- 5444,5473 ----
   * Create a ParamListInfo to pass to SPI
   *
   * We share a single ParamListInfo array across all SPI calls made from this
!  * estate, except calls creating cursors, which use setup_unshared_param_list
!  * (see its comments for reasons why).  A shared array is generally OK since
!  * any given slot in the array would need to contain the same current datum
!  * value no matter which query or expression we're evaluating.  However,
!  * paramLI->parserSetupArg points to the specific PLpgSQL_expr being
!  * evaluated.  This is not an issue for statement-level callers, but
!  * lower-level callers must save and restore estate->paramLI->parserSetupArg
!  * just in case there's an active evaluation at an outer call level.
   *
!  * The general plan for passing parameters to SPI is that plain VAR datums
!  * always have valid images in the shared param list.  This is ensured by
!  * assign_simple_var(), which also marks those params as PARAM_FLAG_CONST,
!  * allowing the planner to use those values in custom plans.  However, non-VAR
!  * datums cannot conveniently be managed that way.  For one thing, they could
!  * throw errors (for example "no such record field") and we do not want that
!  * to happen in a part of the expression that might never be evaluated at
!  * runtime.  For another thing, exec_eval_datum() may return short-lived
!  * values stored in the estate's short-term memory context, which will not
!  * necessarily survive to the next SPI operation.  And for a third thing, ROW
!  * and RECFIELD datums' values depend on other datums, and we don't have a
!  * cheap way to track that.  Therefore, param slots for non-VAR datum types
!  * are always reset here and then filled on-demand by plpgsql_param_fetch().
!  * We can save a few cycles by not bothering with the reset loop unless at
!  * least one such param has actually been filled by plpgsql_param_fetch().
   */
  static ParamListInfo
  setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5488,5515 ****
       */
      if (expr->paramnos)
      {
!         int            dno;
!
!         /* Use the common ParamListInfo for all evals in this estate */
          paramLI = estate->paramLI;

          /*
!          * Reset all entries to "invalid".  It's pretty annoying to have to do
!          * this, but we don't currently track enough information to know which
!          * old entries might be obsolete.  (There are a couple of nontrivial
!          * issues that would have to be dealt with in order to do better here.
!          * First, ROW and RECFIELD datums depend on other datums, and second,
!          * exec_eval_datum() will return short-lived palloc'd values for ROW
!          * and REC datums.)
           */
!         MemSet(paramLI->params, 0, estate->ndatums * sizeof(ParamExternData));

          /*
!          * Instantiate values for "safe" parameters of the expression.  One of
!          * them might be the variable the expression result will be assigned
!          * to, in which case we can pass the variable's value as-is even if
!          * it's a read-write expanded object; otherwise, convert read-write
!          * pointers to read-only pointers for safety.
           */
          dno = -1;
          while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
--- 5488,5589 ----
       */
      if (expr->paramnos)
      {
!         /* Use the common ParamListInfo */
          paramLI = estate->paramLI;

          /*
!          * If any resettable parameters have been passed to the executor since
!          * last time, we need to reset those param slots to "invalid", for the
!          * reasons mentioned in the comment above.
           */
!         if (estate->params_dirty)
!         {
!             Bitmapset  *resettable_datums = estate->func->resettable_datums;
!             int            dno = -1;
!
!             while ((dno = bms_next_member(resettable_datums, dno)) >= 0)
!             {
!                 ParamExternData *prm = ¶mLI->params[dno];
!
!                 prm->ptype = InvalidOid;
!             }
!             estate->params_dirty = false;
!         }

          /*
!          * Set up link to active expr where the hook functions can find it.
!          * Callers must save and restore parserSetupArg if there is any chance
!          * that they are interrupting an active use of parameters.
!          */
!         paramLI->parserSetupArg = (void *) expr;
!
!         /*
!          * Also make sure this is set before parser hooks need it.  There is
!          * no need to save and restore, since the value is always correct once
!          * set.  (Should be set already, but let's be sure.)
!          */
!         expr->func = estate->func;
!     }
!     else
!     {
!         /*
!          * Expression requires no parameters.  Be sure we represent this case
!          * as a NULL ParamListInfo, so that plancache.c knows there is no
!          * point in a custom plan.
!          */
!         paramLI = NULL;
!     }
!     return paramLI;
! }
!
! /*
!  * Create an unshared, short-lived ParamListInfo to pass to SPI
!  *
!  * When creating a cursor, we do not use the shared ParamListInfo array
!  * but create a short-lived one that will contain only params actually
!  * referenced by the query.  The reason for this is that copyParamList() will
!  * be used to copy the parameters into cursor-lifespan storage, and we don't
!  * want it to copy anything that's not used by the specific cursor; that
!  * could result in uselessly copying some large values.
!  *
!  * Caller should pfree the result after use, if it's not NULL.
!  */
! static ParamListInfo
! setup_unshared_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
! {
!     ParamListInfo paramLI;
!
!     /*
!      * We must have created the SPIPlan already (hence, query text has been
!      * parsed/analyzed at least once); else we cannot rely on expr->paramnos.
!      */
!     Assert(expr->plan != NULL);
!
!     /*
!      * We only need a ParamListInfo if the expression has parameters.  In
!      * principle we should test with bms_is_empty(), but we use a not-null
!      * test because it's faster.  In current usage bits are never removed from
!      * expr->paramnos, only added, so this test is correct anyway.
!      */
!     if (expr->paramnos)
!     {
!         int            dno;
!
!         /* initialize ParamListInfo with one entry per datum, all invalid */
!         paramLI = (ParamListInfo)
!             palloc0(offsetof(ParamListInfoData, params) +
!                     estate->ndatums * sizeof(ParamExternData));
!         paramLI->paramFetch = plpgsql_param_fetch;
!         paramLI->paramFetchArg = (void *) estate;
!         paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
!         paramLI->parserSetupArg = (void *) expr;
!         paramLI->numParams = estate->ndatums;
!
!         /*
!          * Instantiate values for "safe" parameters of the expression.  We
!          * could skip this and leave them to be filled by plpgsql_param_fetch;
!          * but then the values would not be available for query planning,
!          * since the planner doesn't call the paramFetch hook.
           */
          dno = -1;
          while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
*************** setup_param_list(PLpgSQL_execstate *esta
*** 5534,5546 ****
          }

          /*
-          * Set up link to active expr where the hook functions can find it.
-          * Callers must save and restore parserSetupArg if there is any chance
-          * that they are interrupting an active use of parameters.
-          */
-         paramLI->parserSetupArg = (void *) expr;
-
-         /*
           * Also make sure this is set before parser hooks need it.  There is
           * no need to save and restore, since the value is always correct once
           * set.  (Should be set already, but let's be sure.)
--- 5608,5613 ----
*************** plpgsql_param_fetch(ParamListInfo params
*** 5581,5600 ****
      expr = (PLpgSQL_expr *) params->parserSetupArg;
      Assert(params->numParams == estate->ndatums);

!     /*
!      * Do nothing if asked for a value that's not supposed to be used by this
!      * SQL expression.  This avoids unwanted evaluations when functions such
!      * as copyParamList try to materialize all the values.
!      */
!     if (!bms_is_member(dno, expr->paramnos))
!         return;

      /* OK, evaluate the value and store into the appropriate paramlist slot */
-     datum = estate->datums[dno];
      prm = ¶ms->params[dno];
      exec_eval_datum(estate, datum,
                      &prm->ptype, &prmtypmod,
                      &prm->value, &prm->isnull);

      /*
       * If it's a read/write expanded datum, convert reference to read-only,
--- 5648,5697 ----
      expr = (PLpgSQL_expr *) params->parserSetupArg;
      Assert(params->numParams == estate->ndatums);

!     /* now we can access the target datum */
!     datum = estate->datums[dno];
!
!     /* need to behave slightly differently for shared and unshared arrays */
!     if (params != estate->paramLI)
!     {
!         /*
!          * We're being called, presumably from copyParamList(), for cursor
!          * parameters.  Since copyParamList() will try to materialize every
!          * single parameter slot, it's important to do nothing when asked for
!          * a datum that's not supposed to be used by this SQL expression.
!          * Otherwise we risk failures in exec_eval_datum(), not to mention
!          * possibly copying a lot more data than the cursor actually uses.
!          */
!         if (!bms_is_member(dno, expr->paramnos))
!             return;
!     }
!     else
!     {
!         /*
!          * Normal evaluation cases.  We don't need to sanity-check dno, but we
!          * do need to mark the shared params array dirty if we're about to
!          * evaluate a resettable datum.
!          */
!         switch (datum->dtype)
!         {
!             case PLPGSQL_DTYPE_ROW:
!             case PLPGSQL_DTYPE_REC:
!             case PLPGSQL_DTYPE_RECFIELD:
!                 estate->params_dirty = true;
!                 break;
!
!             default:
!                 break;
!         }
!     }

      /* OK, evaluate the value and store into the appropriate paramlist slot */
      prm = ¶ms->params[dno];
      exec_eval_datum(estate, datum,
                      &prm->ptype, &prmtypmod,
                      &prm->value, &prm->isnull);
+     /* We can always mark params as "const" for executor's purposes */
+     prm->pflags = PARAM_FLAG_CONST;

      /*
       * If it's a read/write expanded datum, convert reference to read-only,
*************** exec_set_found(PLpgSQL_execstate *estate
*** 6663,6670 ****
      PLpgSQL_var *var;

      var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
!     var->value = BoolGetDatum(state);
!     var->isnull = false;
  }

  /*
--- 6760,6766 ----
      PLpgSQL_var *var;

      var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
!     assign_simple_var(estate, var, BoolGetDatum(state), false, false);
  }

  /*
*************** plpgsql_subxact_cb(SubXactEvent event, S
*** 6799,6812 ****
  }

  /*
!  * free_var --- pfree any pass-by-reference value of the variable.
   *
!  * This should always be followed by some assignment to var->value,
!  * as it leaves a dangling pointer.
   */
  static void
! free_var(PLpgSQL_var *var)
  {
      if (var->freeval)
      {
          if (DatumIsReadWriteExpandedObject(var->value,
--- 6895,6913 ----
  }

  /*
!  * assign_simple_var --- assign a new value to any VAR datum.
   *
!  * This should be the only mechanism for assignment to simple variables,
!  * lest we forget to update the paramLI image.
   */
  static void
! assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
!                   Datum newvalue, bool isnull, bool freeable)
  {
+     ParamExternData *prm;
+
+     Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+     /* Free the old value if needed */
      if (var->freeval)
      {
          if (DatumIsReadWriteExpandedObject(var->value,
*************** free_var(PLpgSQL_var *var)
*** 6815,6834 ****
              DeleteExpandedObject(var->value);
          else
              pfree(DatumGetPointer(var->value));
-         var->freeval = false;
      }
  }

  /*
   * free old value of a text variable and assign new value from C string
   */
  static void
! assign_text_var(PLpgSQL_var *var, const char *str)
  {
!     free_var(var);
!     var->value = CStringGetTextDatum(str);
!     var->isnull = false;
!     var->freeval = true;
  }

  /*
--- 6916,6944 ----
              DeleteExpandedObject(var->value);
          else
              pfree(DatumGetPointer(var->value));
      }
+     /* Assign new value to datum */
+     var->value = newvalue;
+     var->isnull = isnull;
+     var->freeval = freeable;
+     /* And update the image in the common parameter list */
+     prm = &estate->paramLI->params[var->dno];
+     prm->value = MakeExpandedObjectReadOnly(newvalue,
+                                             isnull,
+                                             var->datatype->typlen);
+     prm->isnull = isnull;
+     /* these might be set already, but let's be sure */
+     prm->pflags = PARAM_FLAG_CONST;
+     prm->ptype = var->datatype->typoid;
  }

  /*
   * free old value of a text variable and assign new value from C string
   */
  static void
! assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str)
  {
!     assign_simple_var(estate, var, CStringGetTextDatum(str), false, true);
  }

  /*
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 93c2504..0b78d95 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_function
*** 752,759 ****
--- 752,763 ----
      int            extra_warnings;
      int            extra_errors;

+     /* the datums representing the function's local variables */
      int            ndatums;
      PLpgSQL_datum **datums;
+     Bitmapset  *resettable_datums;        /* dnos of non-simple vars */
+
+     /* function body parsetree */
      PLpgSQL_stmt_block *action;

      /* table for performing casts needed in this function */
*************** typedef struct PLpgSQL_execstate
*** 796,801 ****
--- 800,806 ----

      /* we pass datums[i] to the executor, when needed, in paramLI->params[i] */
      ParamListInfo paramLI;
+     bool        params_dirty;    /* T if any resettable datum has been passed */

      /* EState to use for "simple" expression evaluation */
      EState       *simple_eval_estate;

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

Предыдущее
От: David Christensen
Дата:
Сообщение: Add checksums without --initdb
Следующее
От: Andres Freund
Дата:
Сообщение: Re: Support for N synchronous standby servers - take 2