Commit fd333bc7 authored by Tom Lane's avatar Tom Lane

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
parent 40301c1c
...@@ -607,7 +607,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -607,7 +607,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* Add the variable tg_when */
var = plpgsql_build_variable("tg_when", 0, var = plpgsql_build_variable("tg_when", 0,
...@@ -615,7 +617,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -615,7 +617,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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 */ /* Add the variable tg_level */
var = plpgsql_build_variable("tg_level", 0, var = plpgsql_build_variable("tg_level", 0,
...@@ -623,7 +627,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -623,7 +627,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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 */ /* Add the variable tg_op */
var = plpgsql_build_variable("tg_op", 0, var = plpgsql_build_variable("tg_op", 0,
...@@ -631,7 +637,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -631,7 +637,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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 */ /* Add the variable tg_relid */
var = plpgsql_build_variable("tg_relid", 0, var = plpgsql_build_variable("tg_relid", 0,
...@@ -639,7 +647,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -639,7 +647,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* Add the variable tg_relname */
var = plpgsql_build_variable("tg_relname", 0, var = plpgsql_build_variable("tg_relname", 0,
...@@ -647,7 +657,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -647,7 +657,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* tg_table_name is now preferred to tg_relname */
var = plpgsql_build_variable("tg_table_name", 0, var = plpgsql_build_variable("tg_table_name", 0,
...@@ -655,7 +667,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -655,7 +667,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* add the variable tg_table_schema */
var = plpgsql_build_variable("tg_table_schema", 0, var = plpgsql_build_variable("tg_table_schema", 0,
...@@ -663,7 +677,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -663,7 +677,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* Add the variable tg_nargs */
var = plpgsql_build_variable("tg_nargs", 0, var = plpgsql_build_variable("tg_nargs", 0,
...@@ -671,7 +687,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -671,7 +687,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
InvalidOid), InvalidOid),
true); 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 */ /* Add the variable tg_argv */
var = plpgsql_build_variable("tg_argv", 0, var = plpgsql_build_variable("tg_argv", 0,
...@@ -679,7 +697,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -679,7 +697,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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; break;
...@@ -701,7 +721,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -701,7 +721,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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 */ /* Add the variable tg_tag */
var = plpgsql_build_variable("tg_tag", 0, var = plpgsql_build_variable("tg_tag", 0,
...@@ -709,7 +731,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -709,7 +731,9 @@ do_compile(FunctionCallInfo fcinfo,
-1, -1,
function->fn_input_collation), function->fn_input_collation),
true); 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; break;
...@@ -1878,6 +1902,7 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars) ...@@ -1878,6 +1902,7 @@ build_row_from_vars(PLpgSQL_variable **vars, int numvars)
switch (var->dtype) switch (var->dtype)
{ {
case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
typoid = ((PLpgSQL_var *) var)->datatype->typoid; typoid = ((PLpgSQL_var *) var)->datatype->typoid;
typmod = ((PLpgSQL_var *) var)->datatype->atttypmod; typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
typcoll = ((PLpgSQL_var *) var)->datatype->collation; typcoll = ((PLpgSQL_var *) var)->datatype->collation;
...@@ -2196,6 +2221,7 @@ plpgsql_finish_datums(PLpgSQL_function *function) ...@@ -2196,6 +2221,7 @@ plpgsql_finish_datums(PLpgSQL_function *function)
switch (function->datums[i]->dtype) switch (function->datums[i]->dtype)
{ {
case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
copiable_size += MAXALIGN(sizeof(PLpgSQL_var)); copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
break; break;
case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_REC:
......
This diff is collapsed.
...@@ -729,6 +729,7 @@ plpgsql_free_function_memory(PLpgSQL_function *func) ...@@ -729,6 +729,7 @@ plpgsql_free_function_memory(PLpgSQL_function *func)
switch (d->dtype) switch (d->dtype)
{ {
case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
{ {
PLpgSQL_var *var = (PLpgSQL_var *) d; PLpgSQL_var *var = (PLpgSQL_var *) d;
...@@ -1582,6 +1583,7 @@ plpgsql_dumptree(PLpgSQL_function *func) ...@@ -1582,6 +1583,7 @@ plpgsql_dumptree(PLpgSQL_function *func)
switch (d->dtype) switch (d->dtype)
{ {
case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
{ {
PLpgSQL_var *var = (PLpgSQL_var *) d; PLpgSQL_var *var = (PLpgSQL_var *) d;
...@@ -1608,6 +1610,9 @@ plpgsql_dumptree(PLpgSQL_function *func) ...@@ -1608,6 +1610,9 @@ plpgsql_dumptree(PLpgSQL_function *func)
dump_expr(var->cursor_explicit_expr); dump_expr(var->cursor_explicit_expr);
printf("\n"); printf("\n");
} }
if (var->promise != PLPGSQL_PROMISE_NONE)
printf(" PROMISE %d\n",
(int) var->promise);
} }
break; break;
case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_ROW:
......
...@@ -3170,6 +3170,7 @@ make_return_stmt(int location) ...@@ -3170,6 +3170,7 @@ make_return_stmt(int location)
if (tok == T_DATUM && plpgsql_peek() == ';' && if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR || (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_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)) yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{ {
...@@ -3231,6 +3232,7 @@ make_return_next_stmt(int location) ...@@ -3231,6 +3232,7 @@ make_return_next_stmt(int location)
if (tok == T_DATUM && plpgsql_peek() == ';' && if (tok == T_DATUM && plpgsql_peek() == ';' &&
(yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR || (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_ROW ||
yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)) yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
{ {
...@@ -3318,6 +3320,7 @@ check_assignable(PLpgSQL_datum *datum, int location) ...@@ -3318,6 +3320,7 @@ check_assignable(PLpgSQL_datum *datum, int location)
switch (datum->dtype) switch (datum->dtype)
{ {
case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_VAR:
case PLPGSQL_DTYPE_PROMISE:
if (((PLpgSQL_var *) datum)->isconst) if (((PLpgSQL_var *) datum)->isconst)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_ERROR_IN_ASSIGNMENT), (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
......
...@@ -63,9 +63,29 @@ typedef enum PLpgSQL_datum_type ...@@ -63,9 +63,29 @@ typedef enum PLpgSQL_datum_type
PLPGSQL_DTYPE_ROW, PLPGSQL_DTYPE_ROW,
PLPGSQL_DTYPE_REC, PLPGSQL_DTYPE_REC,
PLPGSQL_DTYPE_RECFIELD, PLPGSQL_DTYPE_RECFIELD,
PLPGSQL_DTYPE_ARRAYELEM PLPGSQL_DTYPE_ARRAYELEM,
PLPGSQL_DTYPE_PROMISE
} PLpgSQL_datum_type; } 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 * Variants distinguished in PLpgSQL_type structs
*/ */
...@@ -248,6 +268,14 @@ typedef struct PLpgSQL_variable ...@@ -248,6 +268,14 @@ typedef struct PLpgSQL_variable
/* /*
* Scalar 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 typedef struct PLpgSQL_var
{ {
...@@ -271,9 +299,18 @@ typedef struct PLpgSQL_var ...@@ -271,9 +299,18 @@ typedef struct PLpgSQL_var
int cursor_explicit_argrow; int cursor_explicit_argrow;
int cursor_options; int cursor_options;
/* Fields below here can change at runtime */
Datum value; Datum value;
bool isnull; bool isnull;
bool freeval; 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; } PLpgSQL_var;
/* /*
...@@ -869,20 +906,6 @@ typedef struct PLpgSQL_function ...@@ -869,20 +906,6 @@ typedef struct PLpgSQL_function
int found_varno; int found_varno;
int new_varno; int new_varno;
int old_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; PLpgSQL_resolve_option resolve_option;
...@@ -912,6 +935,9 @@ typedef struct PLpgSQL_execstate ...@@ -912,6 +935,9 @@ typedef struct PLpgSQL_execstate
{ {
PLpgSQL_function *func; /* function being executed */ PLpgSQL_function *func; /* function being executed */
TriggerData *trigdata; /* if regular trigger, data about firing */
EventTriggerData *evtrigdata; /* if event trigger, data about firing */
Datum retval; Datum retval;
bool retisnull; bool retisnull;
Oid rettype; /* type of current retval */ Oid rettype; /* type of current retval */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment