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
This commit is contained in:
parent
40301c1c8b
commit
fd333bc763
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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:
|
||||
|
@ -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),
|
||||
|
@ -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 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user