Speed up plpgsql trigger startup by introducing "promises".
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2018 00:20:37 +0000 (19:20 -0500)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 14 Feb 2018 00:20:37 +0000 (19:20 -0500)
Over the years we've accreted quite a few special variables that are
predefined in plpgsql trigger functions.  The cost of initializing these
variables to their defined values turns out to be a significant part of
the runtime of simple triggers; but, undoubtedly, most real-world triggers
never examine the values of most of these variables.

To improve matters, invent the notion of a variable that has a "promise"
attached to it, specifying which of the predetermined values should be
assigned to the variable if anything ever reads it.  This eliminates all
the unneeded startup overhead, in return for a small penalty on accesses
to these variables.

Tom Lane, reviewed by Pavel Stehule

Discussion: https://postgr.es/m/11986.1514407114@sss.pgh.pa.us

src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_funcs.c
src/pl/plpgsql/src/pl_gram.y
src/pl/plpgsql/src/plpgsql.h

index 97cb7636413ec96130f6a4ebcb85029fbdf4bf77..526aa8f6219db9147d4ca6fe83f618457bd9328f 100644 (file)
@@ -607,7 +607,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_name_varno = var->dno;
+                       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,
@@ -615,7 +617,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_when_varno = var->dno;
+                       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,
@@ -623,7 +627,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_level_varno = var->dno;
+                       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,
@@ -631,7 +637,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_op_varno = var->dno;
+                       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,
@@ -639,7 +647,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_relid_varno = var->dno;
+                       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,
@@ -647,7 +657,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_relname_varno = var->dno;
+                       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,
@@ -655,7 +667,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_table_name_varno = var->dno;
+                       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,
@@ -663,7 +677,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_table_schema_varno = var->dno;
+                       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,
@@ -671,7 +687,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                InvalidOid),
                                                                                 true);
-                       function->tg_nargs_varno = var->dno;
+                       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,
@@ -679,7 +697,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_argv_varno = var->dno;
+                       Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+                       var->dtype = PLPGSQL_DTYPE_PROMISE;
+                       ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
 
                        break;
 
@@ -701,7 +721,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_event_varno = var->dno;
+                       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,
@@ -709,7 +731,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                                                                                -1,
                                                                                                                                function->fn_input_collation),
                                                                                 true);
-                       function->tg_tag_varno = var->dno;
+                       Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+                       var->dtype = PLPGSQL_DTYPE_PROMISE;
+                       ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;
 
                        break;
 
@@ -1878,6 +1902,7 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
                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;
@@ -2196,6 +2221,7 @@ plpgsql_finish_datums(PLpgSQL_function *function)
                switch (function->datums[i]->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
+                       case PLPGSQL_DTYPE_PROMISE:
                                copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
                                break;
                        case PLPGSQL_DTYPE_REC:
index c90024064a0766e52a9156fb582fdcdf9b3f9f1f..f6866743ac1e7057f9daff5fce190820edbf1065 100644 (file)
@@ -237,6 +237,8 @@ static void coerce_function_result_tuple(PLpgSQL_execstate *estate,
 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);
@@ -547,6 +549,7 @@ plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
                                break;
 
                        default:
+                               /* Anything else should not be an argument variable */
                                elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
                }
        }
@@ -834,10 +837,8 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
 {
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
-       int                     i;
        int                     rc;
        TupleDesc       tupdesc;
-       PLpgSQL_var *var;
        PLpgSQL_rec *rec_new,
                           *rec_old;
        HeapTuple       rettup;
@@ -846,6 +847,7 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
         * Setup the execution state
         */
        plpgsql_estate_setup(&estate, func, NULL, NULL);
+       estate.trigdata = trigdata;
 
        /*
         * Setup error traceback support for ereport()
@@ -906,106 +908,6 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        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");
 
        /*
@@ -1153,12 +1055,12 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
        PLpgSQL_execstate estate;
        ErrorContextCallback plerrcontext;
        int                     rc;
-       PLpgSQL_var *var;
 
        /*
         * Setup the execution state
         */
        plpgsql_estate_setup(&estate, func, NULL, NULL);
+       estate.evtrigdata = trigdata;
 
        /*
         * Setup error traceback support for ereport()
@@ -1174,15 +1076,6 @@ plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
        estate.err_text = gettext_noop("during initialization of execution state");
        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
         */
@@ -1321,6 +1214,7 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
                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));
@@ -1356,6 +1250,166 @@ copy_plpgsql_datums(PLpgSQL_execstate *estate,
        Assert(ws_next == workspace + func->copiable_size);
 }
 
+/*
+ * 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
@@ -1464,6 +1518,10 @@ exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
 
                /*
                 * 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)
                {
@@ -2778,6 +2836,12 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
 
                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;
@@ -2917,6 +2981,12 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
 
                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;
@@ -3487,6 +3557,8 @@ plpgsql_estate_setup(PLpgSQL_execstate *estate,
        func->cur_estate = estate;
 
        estate->func = func;
+       estate->trigdata = NULL;
+       estate->evtrigdata = NULL;
 
        estate->retval = (Datum) 0;
        estate->retisnull = true;
@@ -4542,6 +4614,7 @@ exec_assign_value(PLpgSQL_execstate *estate,
        switch (target->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
+               case PLPGSQL_DTYPE_PROMISE:
                        {
                                /*
                                 * Target is a variable
@@ -4604,10 +4677,16 @@ exec_assign_value(PLpgSQL_execstate *estate,
                                 * 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;
                        }
 
@@ -4951,6 +5030,12 @@ exec_eval_datum(PLpgSQL_execstate *estate,
 
        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;
@@ -5093,6 +5178,7 @@ plpgsql_exec_get_datum_type(PLpgSQL_execstate *estate,
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
+               case PLPGSQL_DTYPE_PROMISE:
                        {
                                PLpgSQL_var *var = (PLpgSQL_var *) datum;
 
@@ -5176,6 +5262,7 @@ plpgsql_exec_get_datum_type_info(PLpgSQL_execstate *estate,
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
+               case PLPGSQL_DTYPE_PROMISE:
                        {
                                PLpgSQL_var *var = (PLpgSQL_var *) datum;
 
@@ -5874,6 +5961,7 @@ plpgsql_param_fetch(ParamListInfo params,
                switch (datum->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
+                       case PLPGSQL_DTYPE_PROMISE:
                                /* always safe */
                                break;
 
@@ -5989,8 +6077,8 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
         * 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.
+        * Currently, only VAR/PROMISE and REC datums could contain read/write
+        * expanded objects.
         */
        if (datum->dtype == PLPGSQL_DTYPE_VAR)
        {
@@ -6002,6 +6090,14 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
        }
        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;
@@ -7680,7 +7776,8 @@ static void
 assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
                                  Datum newvalue, bool isnull, bool freeable)
 {
-       Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+       Assert(var->dtype == PLPGSQL_DTYPE_VAR ||
+                  var->dtype == PLPGSQL_DTYPE_PROMISE);
        /* Free the old value if needed */
        if (var->freeval)
        {
@@ -7695,6 +7792,13 @@ assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
        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;
 }
 
 /*
index b36fab67bc26542ae813c16728aebfd4aed14ee8..379fd69f44a2d9ccaf3eea4a57e57988b6caa917 100644 (file)
@@ -729,6 +729,7 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
                switch (d->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
+                       case PLPGSQL_DTYPE_PROMISE:
                                {
                                        PLpgSQL_var *var = (PLpgSQL_var *) d;
 
@@ -1582,6 +1583,7 @@ plpgsql_dumptree(PLpgSQL_function *func)
                switch (d->dtype)
                {
                        case PLPGSQL_DTYPE_VAR:
+                       case PLPGSQL_DTYPE_PROMISE:
                                {
                                        PLpgSQL_var *var = (PLpgSQL_var *) d;
 
@@ -1608,6 +1610,9 @@ plpgsql_dumptree(PLpgSQL_function *func)
                                                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:
index ee943eed935018bf9f74da25b9788ea3f1d65b72..5bf45942a60ca0d3235c10f577d83f1f5f009bf0 100644 (file)
@@ -3170,6 +3170,7 @@ make_return_stmt(int location)
 
                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))
                {
@@ -3231,6 +3232,7 @@ make_return_next_stmt(int location)
 
                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))
                {
@@ -3318,6 +3320,7 @@ check_assignable(PLpgSQL_datum *datum, int location)
        switch (datum->dtype)
        {
                case PLPGSQL_DTYPE_VAR:
+               case PLPGSQL_DTYPE_PROMISE:
                        if (((PLpgSQL_var *) datum)->isconst)
                                ereport(ERROR,
                                                (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
index dadbfb569c4a12f24342ad82622a2fb777f1bdb4..01b89a5ffa39af761c53b77efa5131d1b5496dc6 100644 (file)
@@ -63,9 +63,29 @@ typedef enum PLpgSQL_datum_type
        PLPGSQL_DTYPE_ROW,
        PLPGSQL_DTYPE_REC,
        PLPGSQL_DTYPE_RECFIELD,
-       PLPGSQL_DTYPE_ARRAYELEM
+       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
  */
@@ -248,6 +268,14 @@ typedef struct PLpgSQL_variable
 
 /*
  * 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
 {
@@ -271,9 +299,18 @@ typedef struct PLpgSQL_var
        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;
 
 /*
@@ -869,20 +906,6 @@ typedef struct PLpgSQL_function
        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;
 
@@ -912,6 +935,9 @@ typedef struct PLpgSQL_execstate
 {
        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 */