From fd333bc763ea104f2a2c10c6b0061c996d4a2f5a Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Tue, 13 Feb 2018 19:20:37 -0500 Subject: [PATCH] Speed up plpgsql trigger startup by introducing "promises". 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 | 50 +++-- src/pl/plpgsql/src/pl_exec.c | 334 ++++++++++++++++++++++------------ src/pl/plpgsql/src/pl_funcs.c | 5 + src/pl/plpgsql/src/pl_gram.y | 3 + src/pl/plpgsql/src/plpgsql.h | 56 ++++-- 5 files changed, 306 insertions(+), 142 deletions(-) diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 97cb763641..526aa8f621 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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: diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index c90024064a..f6866743ac 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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; } /* diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c index b36fab67bc..379fd69f44 100644 --- a/src/pl/plpgsql/src/pl_funcs.c +++ b/src/pl/plpgsql/src/pl_funcs.c @@ -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: diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index ee943eed93..5bf45942a6 100644 --- a/src/pl/plpgsql/src/pl_gram.y +++ b/src/pl/plpgsql/src/pl_gram.y @@ -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), diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index dadbfb569c..01b89a5ffa 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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 */