Commit 8b510838 authored by Tom Lane's avatar Tom Lane

Restructure plpgsql's caching of 'simple' expression evaluation trees

to be less dangerous, and often faster as well.  ExprState trees are
not kept across transaction boundaries; this eliminates problems with
resource leakage in failed transactions.  But by keeping them in a
per-transaction EState, we can safely arrange for a single ExprState
to be shared by all the expression evaluations done in a given plpgsql
function call.  (Formerly it seemed necessary to create and destroy an
ExprState for each exec_eval_simple_expr() call.)  This saves time in
any scenario where a plpgsql function executes more than one expression.
Seems to be about as fast as 7.3 for simple cases, and significantly
faster for functions that do a lot of calculations.
parent 89347900
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.91 2003/09/25 23:02:12 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.92 2003/09/28 23:37:45 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -57,6 +57,19 @@ ...@@ -57,6 +57,19 @@
static const char *const raise_skip_msg = "RAISE"; static const char *const raise_skip_msg = "RAISE";
/*
* All plpgsql function executions within a single transaction share
* the same executor EState for evaluating "simple" expressions. Each
* function call creates its own "eval_econtext" ExprContext within this
* estate. We destroy the estate at transaction shutdown to ensure there
* is no permanent leakage of memory (especially for xact abort case).
*
* If a simple PLpgSQL_expr has been used in the current xact, it is
* linked into the active_simple_exprs list.
*/
static EState *simple_eval_estate = NULL;
static PLpgSQL_expr *active_simple_exprs = NULL;
/************************************************************ /************************************************************
* Local function forward declarations * Local function forward declarations
************************************************************/ ************************************************************/
...@@ -135,7 +148,7 @@ static void exec_eval_datum(PLpgSQL_execstate * estate, ...@@ -135,7 +148,7 @@ static void exec_eval_datum(PLpgSQL_execstate * estate,
Oid *typeid, Oid *typeid,
Datum *value, Datum *value,
bool *isnull); bool *isnull);
static int exec_eval_subscript(PLpgSQL_execstate * estate, static int exec_eval_integer(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr, PLpgSQL_expr * expr,
bool *isNull); bool *isNull);
static Datum exec_eval_expr(PLpgSQL_execstate * estate, static Datum exec_eval_expr(PLpgSQL_execstate * estate,
...@@ -381,6 +394,9 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo) ...@@ -381,6 +394,9 @@ plpgsql_exec_function(PLpgSQL_function * func, FunctionCallInfo fcinfo)
} }
/* Clean up any leftover temporary memory */ /* Clean up any leftover temporary memory */
if (estate.eval_econtext != NULL)
FreeExprContext(estate.eval_econtext);
estate.eval_econtext = NULL;
exec_eval_cleanup(&estate); exec_eval_cleanup(&estate);
/* /*
...@@ -653,6 +669,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func, ...@@ -653,6 +669,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
} }
/* Clean up any leftover temporary memory */ /* Clean up any leftover temporary memory */
if (estate.eval_econtext != NULL)
FreeExprContext(estate.eval_econtext);
estate.eval_econtext = NULL;
exec_eval_cleanup(&estate); exec_eval_cleanup(&estate);
/* /*
...@@ -1915,10 +1934,9 @@ exec_eval_cleanup(PLpgSQL_execstate * estate) ...@@ -1915,10 +1934,9 @@ exec_eval_cleanup(PLpgSQL_execstate * estate)
SPI_freetuptable(estate->eval_tuptable); SPI_freetuptable(estate->eval_tuptable);
estate->eval_tuptable = NULL; estate->eval_tuptable = NULL;
/* Clear result of exec_eval_simple_expr */ /* Clear result of exec_eval_simple_expr (but keep the econtext) */
if (estate->eval_econtext != NULL) if (estate->eval_econtext != NULL)
FreeExprContext(estate->eval_econtext); ResetExprContext(estate->eval_econtext);
estate->eval_econtext = NULL;
} }
...@@ -1962,7 +1980,7 @@ exec_prepare_plan(PLpgSQL_execstate * estate, ...@@ -1962,7 +1980,7 @@ exec_prepare_plan(PLpgSQL_execstate * estate,
expr->plan = SPI_saveplan(plan); expr->plan = SPI_saveplan(plan);
spi_plan = (_SPI_plan *) expr->plan; spi_plan = (_SPI_plan *) expr->plan;
expr->plan_argtypes = spi_plan->argtypes; expr->plan_argtypes = spi_plan->argtypes;
expr->plan_simple_expr = NULL; expr->expr_simple_expr = NULL;
exec_simple_check_plan(expr); exec_simple_check_plan(expr);
SPI_freeplan(plan); SPI_freeplan(plan);
...@@ -2931,7 +2949,7 @@ exec_assign_value(PLpgSQL_execstate * estate, ...@@ -2931,7 +2949,7 @@ exec_assign_value(PLpgSQL_execstate * estate,
bool subisnull; bool subisnull;
subscriptvals[i] = subscriptvals[i] =
exec_eval_subscript(estate, exec_eval_integer(estate,
subscripts[nsubscripts - 1 - i], subscripts[nsubscripts - 1 - i],
&subisnull); &subisnull);
havenullsubscript |= subisnull; havenullsubscript |= subisnull;
...@@ -3065,7 +3083,7 @@ exec_eval_datum(PLpgSQL_execstate * estate, ...@@ -3065,7 +3083,7 @@ exec_eval_datum(PLpgSQL_execstate * estate,
case PLPGSQL_DTYPE_TRIGARG: case PLPGSQL_DTYPE_TRIGARG:
trigarg = (PLpgSQL_trigarg *) datum; trigarg = (PLpgSQL_trigarg *) datum;
*typeid = TEXTOID; *typeid = TEXTOID;
tgargno = exec_eval_subscript(estate, trigarg->argnum, isnull); tgargno = exec_eval_integer(estate, trigarg->argnum, isnull);
if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs) if (*isnull || tgargno < 0 || tgargno >= estate->trig_nargs)
{ {
*value = (Datum) 0; *value = (Datum) 0;
...@@ -3089,33 +3107,28 @@ exec_eval_datum(PLpgSQL_execstate * estate, ...@@ -3089,33 +3107,28 @@ exec_eval_datum(PLpgSQL_execstate * estate,
} }
/* ---------- /* ----------
* exec_eval_subscript Hack to allow subscripting of result variables. * exec_eval_integer Evaluate an expression, coerce result to int4
* *
* The caller may already have an open eval_econtext, which we have to * Note we do not do exec_eval_cleanup here; the caller must do it at
* save and restore around the call of exec_eval_expr. * some later point. (We do this because the caller may be holding the
* results of other, pass-by-reference, expression evaluations, such as
* an array value to be subscripted. Also see notes in exec_eval_simple_expr
* about allocation of the parameter array.)
* ---------- * ----------
*/ */
static int static int
exec_eval_subscript(PLpgSQL_execstate * estate, exec_eval_integer(PLpgSQL_execstate * estate,
PLpgSQL_expr * expr, PLpgSQL_expr * expr,
bool *isNull) bool *isNull)
{ {
ExprContext *save_econtext; Datum exprdatum;
Datum subscriptdatum; Oid exprtypeid;
Oid subscripttypeid;
int result;
save_econtext = estate->eval_econtext; exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid);
estate->eval_econtext = NULL; exprdatum = exec_simple_cast_value(exprdatum, exprtypeid,
subscriptdatum = exec_eval_expr(estate, expr, isNull, &subscripttypeid);
subscriptdatum = exec_simple_cast_value(subscriptdatum,
subscripttypeid,
INT4OID, -1, INT4OID, -1,
isNull); isNull);
result = DatumGetInt32(subscriptdatum); return DatumGetInt32(exprdatum);
exec_eval_cleanup(estate);
estate->eval_econtext = save_econtext;
return result;
} }
/* ---------- /* ----------
...@@ -3143,7 +3156,7 @@ exec_eval_expr(PLpgSQL_execstate * estate, ...@@ -3143,7 +3156,7 @@ exec_eval_expr(PLpgSQL_execstate * estate,
* If this is a simple expression, bypass SPI and use the executor * If this is a simple expression, bypass SPI and use the executor
* directly * directly
*/ */
if (expr->plan_simple_expr != NULL) if (expr->expr_simple_expr != NULL)
return exec_eval_simple_expr(estate, expr, isNull, rettype); return exec_eval_simple_expr(estate, expr, isNull, rettype);
rc = exec_run_select(estate, expr, 2, NULL); rc = exec_run_select(estate, expr, 2, NULL);
...@@ -3262,6 +3275,10 @@ exec_run_select(PLpgSQL_execstate * estate, ...@@ -3262,6 +3275,10 @@ exec_run_select(PLpgSQL_execstate * estate,
/* ---------- /* ----------
* exec_eval_simple_expr - Evaluate a simple expression returning * exec_eval_simple_expr - Evaluate a simple expression returning
* a Datum by directly calling ExecEvalExpr(). * a Datum by directly calling ExecEvalExpr().
*
* Note: if pass-by-reference, the result is in the eval_econtext's
* temporary memory context. It will be freed when exec_eval_cleanup
* is done.
* ---------- * ----------
*/ */
static Datum static Datum
...@@ -3271,64 +3288,97 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate, ...@@ -3271,64 +3288,97 @@ exec_eval_simple_expr(PLpgSQL_execstate * estate,
Oid *rettype) Oid *rettype)
{ {
Datum retval; Datum retval;
int i;
ExprContext *econtext; ExprContext *econtext;
ParamListInfo paramLI; ParamListInfo paramLI;
int i;
/*
* Pass back previously-determined result type.
*/
*rettype = expr->expr_simple_type;
/* /*
* Create an expression context to hold the arguments and the result * Create an EState for evaluation of simple expressions, if there's
* of this expression evaluation. This must be a child of the EState * not one already in the current transaction. The EState is made a
* we created in the SPI plan's context. * child of TopTransactionContext so it will have the right lifespan.
*/ */
econtext = CreateExprContext(expr->plan_simple_estate); if (simple_eval_estate == NULL)
{
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(TopTransactionContext);
simple_eval_estate = CreateExecutorState();
MemoryContextSwitchTo(oldcontext);
}
/*
* Prepare the expression for execution, if it's not been done already
* in the current transaction.
*/
if (expr->expr_simple_state == NULL)
{
expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
simple_eval_estate);
/* Add it to list for cleanup */
expr->expr_simple_next = active_simple_exprs;
active_simple_exprs = expr;
}
/*
* Create an expression context for simple expressions, if there's
* not one already in the current function call. This must be a
* child of simple_eval_estate.
*/
econtext = estate->eval_econtext;
if (econtext == NULL)
{
econtext = CreateExprContext(simple_eval_estate);
estate->eval_econtext = econtext;
}
/* /*
* Param list can live in econtext's temporary memory context. * Param list can live in econtext's temporary memory context.
*
* XXX think about avoiding repeated palloc's for param lists?
* Beware however that this routine is re-entrant: exec_eval_datum()
* can call it back for subscript evaluation, and so there can be a
* need to have more than one active param list.
*/ */
paramLI = (ParamListInfo) paramLI = (ParamListInfo)
MemoryContextAlloc(econtext->ecxt_per_tuple_memory, MemoryContextAlloc(econtext->ecxt_per_tuple_memory,
(expr->nparams + 1) * sizeof(ParamListInfoData)); (expr->nparams + 1) * sizeof(ParamListInfoData));
econtext->ecxt_param_list_info = paramLI;
/* /*
* Put the parameter values into the parameter list info of the * Put the parameter values into the parameter list entries.
* expression context.
*/ */
for (i = 0; i < expr->nparams; i++, paramLI++) for (i = 0; i < expr->nparams; i++)
{ {
PLpgSQL_datum *datum = estate->datums[expr->params[i]]; PLpgSQL_datum *datum = estate->datums[expr->params[i]];
Oid paramtypeid; Oid paramtypeid;
paramLI->kind = PARAM_NUM; paramLI[i].kind = PARAM_NUM;
paramLI->id = i + 1; paramLI[i].id = i + 1;
exec_eval_datum(estate, datum, expr->plan_argtypes[i], exec_eval_datum(estate, datum, expr->plan_argtypes[i],
&paramtypeid, &paramLI->value, &paramLI->isnull); &paramtypeid,
&paramLI[i].value, &paramLI[i].isnull);
} }
paramLI->kind = PARAM_INVALID; paramLI[i].kind = PARAM_INVALID;
/* /*
* Initialize things * Now we can safely make the econtext point to the param list.
*/ */
*rettype = expr->plan_simple_type; econtext->ecxt_param_list_info = paramLI;
/* /*
* Now call the executor to evaluate the expression * Now call the executor to evaluate the expression
*/ */
SPI_push(); SPI_push();
retval = ExecEvalExprSwitchContext(expr->plan_simple_expr, retval = ExecEvalExprSwitchContext(expr->expr_simple_state,
econtext, econtext,
isNull, isNull,
NULL); NULL);
SPI_pop(); SPI_pop();
/*
* Note: if pass-by-reference, the result is in the econtext's
* temporary memory context. It will be freed when exec_eval_cleanup
* is done.
*/
Assert(estate->eval_econtext == NULL);
estate->eval_econtext = econtext;
/* /*
* That's it. * That's it.
*/ */
...@@ -3795,9 +3845,8 @@ exec_simple_check_plan(PLpgSQL_expr * expr) ...@@ -3795,9 +3845,8 @@ exec_simple_check_plan(PLpgSQL_expr * expr)
_SPI_plan *spi_plan = (_SPI_plan *) expr->plan; _SPI_plan *spi_plan = (_SPI_plan *) expr->plan;
Plan *plan; Plan *plan;
TargetEntry *tle; TargetEntry *tle;
MemoryContext oldcontext;
expr->plan_simple_expr = NULL; expr->expr_simple_expr = NULL;
/* /*
* 1. We can only evaluate queries that resulted in one single * 1. We can only evaluate queries that resulted in one single
...@@ -3842,21 +3891,14 @@ exec_simple_check_plan(PLpgSQL_expr * expr) ...@@ -3842,21 +3891,14 @@ exec_simple_check_plan(PLpgSQL_expr * expr)
return; return;
/* /*
* Yes - this is a simple expression. Prepare to execute it. We need * Yes - this is a simple expression. Mark it as such, and initialize
* an EState and an expression state tree, which we'll put into the * state to "not executing".
* plan context so they will have appropriate lifespan.
*/ */
oldcontext = MemoryContextSwitchTo(spi_plan->plancxt); expr->expr_simple_expr = tle->expr;
expr->expr_simple_state = NULL;
expr->plan_simple_estate = CreateExecutorState(); expr->expr_simple_next = NULL;
expr->plan_simple_expr = ExecPrepareExpr(tle->expr,
expr->plan_simple_estate);
MemoryContextSwitchTo(oldcontext);
/* Also stash away the expression result type */ /* Also stash away the expression result type */
expr->plan_simple_type = exprType((Node *) tle->expr); expr->expr_simple_type = exprType((Node *) tle->expr);
} }
/* /*
...@@ -3893,3 +3935,35 @@ exec_set_found(PLpgSQL_execstate * estate, bool state) ...@@ -3893,3 +3935,35 @@ exec_set_found(PLpgSQL_execstate * estate, bool state)
var->value = (Datum) state; var->value = (Datum) state;
var->isnull = false; var->isnull = false;
} }
/*
* plpgsql_eoxact --- post-transaction-commit-or-abort cleanup
*
* If a simple_eval_estate was created in the current transaction,
* it has to be cleaned up, and we have to mark all active PLpgSQL_expr
* structs that are using it as no longer active.
*/
void
plpgsql_eoxact(bool isCommit, void *arg)
{
PLpgSQL_expr *expr;
PLpgSQL_expr *enext;
/* Mark all active exprs as inactive */
for (expr = active_simple_exprs; expr; expr = enext)
{
enext = expr->expr_simple_next;
expr->expr_simple_state = NULL;
expr->expr_simple_next = NULL;
}
active_simple_exprs = NULL;
/*
* If we are doing a clean transaction shutdown, free the EState
* (so that any remaining resources will be released correctly).
* In an abort, we expect the regular abort recovery procedures to
* release everything of interest.
*/
if (isCommit && simple_eval_estate)
FreeExecutorState(simple_eval_estate);
simple_eval_estate = NULL;
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.17 2003/08/04 00:43:33 momjian Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_handler.c,v 1.18 2003/09/28 23:37:45 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -46,7 +46,6 @@ ...@@ -46,7 +46,6 @@
static int plpgsql_firstcall = 1; static int plpgsql_firstcall = 1;
void plpgsql_init(void);
static void plpgsql_init_all(void); static void plpgsql_init_all(void);
...@@ -64,6 +63,8 @@ plpgsql_init(void) ...@@ -64,6 +63,8 @@ plpgsql_init(void)
plpgsql_HashTableInit(); plpgsql_HashTableInit();
RegisterEOXactCallback(plpgsql_eoxact, NULL);
plpgsql_firstcall = 0; plpgsql_firstcall = 0;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* procedural language * procedural language
* *
* IDENTIFICATION * IDENTIFICATION
* $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.41 2003/09/25 23:02:12 tgl Exp $ * $Header: /cvsroot/pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.42 2003/09/28 23:37:45 tgl Exp $
* *
* This software is copyrighted by Jan Wieck - Hamburg. * This software is copyrighted by Jan Wieck - Hamburg.
* *
...@@ -166,18 +166,22 @@ typedef struct ...@@ -166,18 +166,22 @@ typedef struct
} PLpgSQL_datum; } PLpgSQL_datum;
typedef struct typedef struct PLpgSQL_expr
{ /* SQL Query to plan and execute */ { /* SQL Query to plan and execute */
int dtype; int dtype;
int exprno; int exprno;
char *query; char *query;
void *plan; void *plan;
ExprState *plan_simple_expr;
EState *plan_simple_estate;
Oid plan_simple_type;
Oid *plan_argtypes; Oid *plan_argtypes;
/* fields for "simple expression" fast-path execution: */
Expr *expr_simple_expr; /* NULL means not a simple expr */
Oid expr_simple_type;
/* if expr is simple AND in use in current xact, these fields are set: */
ExprState *expr_simple_state;
struct PLpgSQL_expr *expr_simple_next;
/* params to pass to expr */
int nparams; int nparams;
int params[1]; int params[1]; /* VARIABLE SIZE ARRAY ... must be last */
} PLpgSQL_expr; } PLpgSQL_expr;
...@@ -636,6 +640,7 @@ extern void plpgsql_HashTableInit(void); ...@@ -636,6 +640,7 @@ extern void plpgsql_HashTableInit(void);
* Functions in pl_handler.c * Functions in pl_handler.c
* ---------- * ----------
*/ */
extern void plpgsql_init(void);
extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS); extern Datum plpgsql_call_handler(PG_FUNCTION_ARGS);
/* ---------- /* ----------
...@@ -646,7 +651,7 @@ extern Datum plpgsql_exec_function(PLpgSQL_function * func, ...@@ -646,7 +651,7 @@ extern Datum plpgsql_exec_function(PLpgSQL_function * func,
FunctionCallInfo fcinfo); FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function * func, extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function * func,
TriggerData *trigdata); TriggerData *trigdata);
extern void plpgsql_eoxact(bool isCommit, void *arg);
/* ---------- /* ----------
* Functions for the dynamic string handling in pl_funcs.c * Functions for the dynamic string handling in pl_funcs.c
......
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