simplify-simple-expressions.patch
text/x-patch
Filename: simplify-simple-expressions.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
| File | + | − |
|---|---|---|
| src/pl/plpgsql/src/gram.y | 0 | 0 |
| src/pl/plpgsql/src/pl_comp.c | 0 | 0 |
| src/pl/plpgsql/src/pl_exec.c | 0 | 0 |
| src/pl/plpgsql/src/pl_handler.c | 0 | 0 |
| src/pl/plpgsql/src/plpgsql.h | 0 | 0 |
diff --git a/src/pl/plpgsql/src/gram.y b/src/pl/plpgsql/src/gram.y
index a28c6707e4670be17ed1c947d383f1d11b9c90c5..0963efe1b5b5e7e974a682d2a7f71d6427180e9b 100644
*** a/src/pl/plpgsql/src/gram.y
--- b/src/pl/plpgsql/src/gram.y
*************** decl_statement : decl_varname decl_const
*** 490,495 ****
--- 490,496 ----
curname_def = palloc0(sizeof(PLpgSQL_expr));
curname_def->dtype = PLPGSQL_DTYPE_EXPR;
+ curname_def->expr_num = plpgsql_nExprs++;
strcpy(buf, "SELECT ");
cp1 = new->refname;
cp2 = buf + strlen(buf);
*************** read_sql_construct(int until,
*** 2277,2282 ****
--- 2278,2284 ----
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->expr_num = plpgsql_nExprs++;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
*************** make_execsql_stmt(int firsttoken, int lo
*** 2476,2481 ****
--- 2478,2484 ----
expr->query = pstrdup(ds.data);
expr->plan = NULL;
expr->paramnos = NULL;
+ expr->expr_num = plpgsql_nExprs++;
expr->ns = plpgsql_ns_top();
pfree(ds.data);
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 3ddcf3b5a595e0847627cc10c4084258f44cc352..4feb14d2bec87de5d8a66071ab3a66da3c2a2d76 100644
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 37,42 ****
--- 37,47 ----
/* ----------
* Our own local and global variables
+ *
+ * Ideally these would live in some dynamically-allocated structure, but
+ * it's unpleasant to pass such a thing around in a Bison parser. As long
+ * as plpgsql function compilation isn't re-entrant, it's okay to use
+ * static storage for these.
* ----------
*/
PLpgSQL_stmt_block *plpgsql_parse_result;
*************** int plpgsql_nDatums;
*** 46,51 ****
--- 51,58 ----
PLpgSQL_datum **plpgsql_Datums;
static int datums_last = 0;
+ int plpgsql_nExprs;
+
char *plpgsql_error_funcname;
bool plpgsql_DumpExecTree = false;
bool plpgsql_check_syntax = false;
*************** do_compile(FunctionCallInfo fcinfo,
*** 367,372 ****
--- 374,381 ----
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
+ plpgsql_nExprs = 0;
+
switch (is_trigger)
{
case false:
*************** do_compile(FunctionCallInfo fcinfo,
*** 693,698 ****
--- 702,708 ----
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
for (i = 0; i < plpgsql_nDatums; i++)
function->datums[i] = plpgsql_Datums[i];
+ function->nexprs = plpgsql_nExprs;
/* Debug dump for completed functions */
if (plpgsql_DumpExecTree)
*************** plpgsql_compile_inline(char *proc_source
*** 788,793 ****
--- 798,804 ----
plpgsql_nDatums = 0;
plpgsql_Datums = palloc(sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
+ plpgsql_nExprs = 0;
/* Set up as though in a function returning VOID */
function->fn_rettype = VOIDOID;
*************** plpgsql_compile_inline(char *proc_source
*** 838,843 ****
--- 849,855 ----
function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
for (i = 0; i < plpgsql_nDatums; i++)
function->datums[i] = plpgsql_Datums[i];
+ function->nexprs = plpgsql_nExprs;
/*
* Pop the error context stack
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 9929e04e57bbc7d08938765b7f506c05c07b772b..613b96c0cc43b60ce770ea239184f686287a996c 100644
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
*************** typedef struct
*** 48,80 ****
bool *freevals; /* which arguments are pfree-able */
} PreparedParamsData;
- /*
- * 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 for
- * per-evaluation workspace. eval_econtext is freed at normal function exit,
- * and the EState is freed at transaction end (in case of error, we assume
- * that the abort mechanisms clean it all up). Furthermore, any exception
- * block within a function has to have its own eval_econtext separate from
- * the containing function's, so that we can clean up ExprContext callbacks
- * properly at subtransaction exit. We maintain a stack that tracks the
- * individual econtexts so that we can clean up correctly at subxact exit.
- *
- * This arrangement is a bit tedious to maintain, but it's worth the trouble
- * so that we don't have to re-prepare simple expressions on each trip through
- * a function. (We assume the case to optimize is many repetitions of a
- * function within a transaction.)
- */
- typedef struct SimpleEcontextStackEntry
- {
- ExprContext *stack_econtext; /* a stacked econtext */
- SubTransactionId xact_subxid; /* ID for current subxact */
- struct SimpleEcontextStackEntry *next; /* next stack entry up */
- } SimpleEcontextStackEntry;
-
- static EState *simple_eval_estate = NULL;
- static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
-
/************************************************************
* Local function forward declarations
************************************************************/
--- 48,53 ----
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1013,1019 ****
--- 986,994 ----
*/
MemoryContext oldcontext = CurrentMemoryContext;
ResourceOwner oldowner = CurrentResourceOwner;
+ EState *old_eval_estate = estate->eval_estate;
ExprContext *old_eval_econtext = estate->eval_econtext;
+ ExprState **old_eval_exprtrees = estate->eval_exprtrees;
ErrorData *save_cur_error = estate->cur_error;
estate->err_text = gettext_noop("during statement block entry");
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1026,1034 ****
{
/*
* We need to run the block's statements with a new eval_econtext
! * that belongs to the current subtransaction; if we try to use
! * the outer econtext then ExprContext shutdown callbacks will be
! * called at the wrong times.
*/
plpgsql_create_econtext(estate);
--- 1001,1012 ----
{
/*
* We need to run the block's statements with a new eval_econtext
! * that is private to the current subtransaction. If we try to
! * share the outer econtext we won't be able to call ExprContext
! * shutdown callbacks for just the exprs used inside the
! * subtransaction. Also, we need to be able to release the
! * exprtrees on subtransaction failure, since they might contain
! * stale execution state in that case.
*/
plpgsql_create_econtext(estate);
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1058,1075 ****
resTypByVal, resTypLen);
}
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
- * Revert to outer eval_econtext. (The inner one was
- * automatically cleaned up during subxact exit.)
- */
- estate->eval_econtext = old_eval_econtext;
-
- /*
* AtEOSubXact_SPI() should not have popped any SPI context, but
* just in case it did, make sure we remain connected.
*/
--- 1036,1053 ----
resTypByVal, resTypLen);
}
+ /* Clean up the inner eval_econtext, and restore the outer */
+ plpgsql_destroy_econtext(estate);
+ estate->eval_estate = old_eval_estate;
+ estate->eval_econtext = old_eval_econtext;
+ estate->eval_exprtrees = old_eval_exprtrees;
+
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
/*
* AtEOSubXact_SPI() should not have popped any SPI context, but
* just in case it did, make sure we remain connected.
*/
*************** exec_stmt_block(PLpgSQL_execstate *estat
*** 1092,1099 ****
--- 1070,1088 ----
MemoryContextSwitchTo(oldcontext);
CurrentResourceOwner = oldowner;
+ /*
+ * We don't want to run cleanup hooks for the inner eval_econtext,
+ * but we do want to free the memory it used. Beware of
+ * possibility that we failed during sub-commit after the inner
+ * econtext was already cleaned up.
+ */
+ if (estate->eval_estate != old_eval_estate)
+ MemoryContextDelete(estate->eval_estate->es_query_cxt);
+
/* Revert to outer eval_econtext */
+ estate->eval_estate = old_eval_estate;
estate->eval_econtext = old_eval_econtext;
+ estate->eval_exprtrees = old_eval_exprtrees;
/*
* If AtEOSubXact_SPI() popped any SPI context of the subxact, it
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 2670,2679 ****
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
/* caller is expected to fill the datums array */
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
- estate->eval_econtext = NULL;
estate->cur_expr = NULL;
estate->err_stmt = NULL;
--- 2659,2671 ----
estate->datums = palloc(sizeof(PLpgSQL_datum *) * estate->ndatums);
/* caller is expected to fill the datums array */
+ estate->eval_estate = NULL;
+ estate->eval_econtext = NULL;
+ estate->eval_exprtrees = NULL;
+
estate->eval_tuptable = NULL;
estate->eval_processed = 0;
estate->eval_lastoid = InvalidOid;
estate->cur_expr = NULL;
estate->err_stmt = NULL;
*************** plpgsql_estate_setup(PLpgSQL_execstate *
*** 2682,2688 ****
estate->plugin_info = NULL;
/*
! * Create an EState and ExprContext for evaluation of simple expressions.
*/
plpgsql_create_econtext(estate);
--- 2674,2680 ----
estate->plugin_info = NULL;
/*
! * Initialize state for evaluation of simple expressions.
*/
plpgsql_create_econtext(estate);
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4514,4520 ****
Oid *rettype)
{
ExprContext *econtext = estate->eval_econtext;
! LocalTransactionId curlxid = MyProc->lxid;
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
--- 4506,4512 ----
Oid *rettype)
{
ExprContext *econtext = estate->eval_econtext;
! ExprState **exprtree_p;
CachedPlanSource *plansource;
CachedPlan *cplan;
ParamListInfo paramLI;
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4546,4551 ****
--- 4538,4545 ----
ReleaseCachedPlan(cplan, true);
return false;
}
+ /* Force rebuild of execution tree below */
+ estate->eval_exprtrees[expr->expr_num] = NULL;
}
/*
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4555,4568 ****
/*
* Prepare the expression for execution, if it's not been done already in
! * the current transaction. (This will be forced to happen if we called
! * exec_simple_check_plan above.)
*/
! if (expr->expr_simple_lxid != curlxid)
{
! expr->expr_simple_state = ExecPrepareExpr(expr->expr_simple_expr,
! simple_eval_estate);
! expr->expr_simple_lxid = curlxid;
}
/*
--- 4549,4571 ----
/*
* Prepare the expression for execution, if it's not been done already in
! * the current function.
*/
! exprtree_p = &estate->eval_exprtrees[expr->expr_num];
! if (*exprtree_p == NULL)
{
! #if 0
! *exprtree_p = ExecPrepareExpr(expr->expr_simple_expr,
! estate->eval_estate);
! #else
! MemoryContext oldcontext;
!
! oldcontext = MemoryContextSwitchTo(estate->eval_estate->es_query_cxt);
!
! *exprtree_p = ExecInitExpr(expr->expr_simple_expr, NULL);
!
! MemoryContextSwitchTo(oldcontext);
! #endif
}
/*
*************** exec_eval_simple_expr(PLpgSQL_execstate
*** 4599,4605 ****
/*
* Finally we can call the executor to evaluate the expression
*/
! *result = ExecEvalExpr(expr->expr_simple_state,
econtext,
isNull,
NULL);
--- 4602,4608 ----
/*
* Finally we can call the executor to evaluate the expression
*/
! *result = ExecEvalExpr(*exprtree_p,
econtext,
isNull,
NULL);
*************** exec_simple_check_plan(PLpgSQL_expr *exp
*** 5336,5348 ****
return;
/*
! * Yes - this is a simple expression. Mark it as such, and initialize
! * state to "not valid in current transaction".
*/
expr->expr_simple_expr = tle->expr;
- expr->expr_simple_state = NULL;
- expr->expr_simple_lxid = InvalidLocalTransactionId;
- /* Also stash away the expression result type */
expr->expr_simple_type = exprType((Node *) tle->expr);
}
--- 5339,5348 ----
return;
/*
! * Yes - this is a simple expression. Mark it as such, and also
! * remember the expression's result type.
*/
expr->expr_simple_expr = tle->expr;
expr->expr_simple_type = exprType((Node *) tle->expr);
}
*************** exec_set_found(PLpgSQL_execstate *estate
*** 5363,5491 ****
/*
* plpgsql_create_econtext --- create an eval_econtext for the current function
- *
- * We may need to create a new simple_eval_estate too, if there's not one
- * already for the current transaction. The EState will be cleaned up at
- * transaction end.
*/
static void
plpgsql_create_econtext(PLpgSQL_execstate *estate)
{
- SimpleEcontextStackEntry *entry;
-
/*
! * Create an EState for evaluation of simple expressions, if there's not
! * one already in the current transaction. The EState is made a child of
! * TopTransactionContext so it will have the right lifespan.
! */
! if (simple_eval_estate == NULL)
! {
! MemoryContext oldcontext;
!
! oldcontext = MemoryContextSwitchTo(TopTransactionContext);
! simple_eval_estate = CreateExecutorState();
! MemoryContextSwitchTo(oldcontext);
! }
!
! /*
! * Create a child econtext for the current function.
*/
! estate->eval_econtext = CreateExprContext(simple_eval_estate);
/*
! * Make a stack entry so we can clean up the econtext at subxact end.
! * Stack entries are kept in TopTransactionContext for simplicity.
*/
! entry = (SimpleEcontextStackEntry *)
! MemoryContextAlloc(TopTransactionContext,
! sizeof(SimpleEcontextStackEntry));
!
! entry->stack_econtext = estate->eval_econtext;
! entry->xact_subxid = GetCurrentSubTransactionId();
!
! entry->next = simple_econtext_stack;
! simple_econtext_stack = entry;
}
/*
* plpgsql_destroy_econtext --- destroy function's econtext
- *
- * We check that it matches the top stack entry, and destroy the stack
- * entry along with the context.
*/
static void
plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
{
! SimpleEcontextStackEntry *next;
!
! Assert(simple_econtext_stack != NULL);
! Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
!
! next = simple_econtext_stack->next;
! pfree(simple_econtext_stack);
! simple_econtext_stack = next;
!
! FreeExprContext(estate->eval_econtext, true);
estate->eval_econtext = NULL;
! }
!
! /*
! * plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
! *
! * If a simple-expression EState was created in the current transaction,
! * it has to be cleaned up.
! */
! void
! plpgsql_xact_cb(XactEvent event, void *arg)
! {
! /*
! * 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 (event != XACT_EVENT_ABORT)
! {
! /* Shouldn't be any econtext stack entries left at commit */
! Assert(simple_econtext_stack == NULL);
!
! if (simple_eval_estate)
! FreeExecutorState(simple_eval_estate);
! simple_eval_estate = NULL;
! }
! else
! {
! simple_econtext_stack = NULL;
! simple_eval_estate = NULL;
! }
! }
!
! /*
! * plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
! *
! * Make sure any simple-expression econtexts created in the current
! * subtransaction get cleaned up. We have to do this explicitly because
! * no other code knows which child econtexts of simple_eval_estate belong
! * to which level of subxact.
! */
! void
! plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
! SubTransactionId parentSubid, void *arg)
! {
! if (event == SUBXACT_EVENT_START_SUB)
! return;
!
! while (simple_econtext_stack != NULL &&
! simple_econtext_stack->xact_subxid == mySubid)
! {
! SimpleEcontextStackEntry *next;
!
! FreeExprContext(simple_econtext_stack->stack_econtext,
! (event == SUBXACT_EVENT_COMMIT_SUB));
! next = simple_econtext_stack->next;
! pfree(simple_econtext_stack);
! simple_econtext_stack = next;
! }
}
/*
--- 5363,5402 ----
/*
* plpgsql_create_econtext --- create an eval_econtext for the current function
*/
static void
plpgsql_create_econtext(PLpgSQL_execstate *estate)
{
/*
! * Create an EState and an ExprContext for evaluation of simple
! * expressions. These are children of the function's SPI-procedure memory
! * context, so they will live only until function exit.
*/
! estate->eval_estate = CreateExecutorState();
! estate->eval_econtext = CreateExprContext(estate->eval_estate);
/*
! * Create space for per-PLpgSQL_expr expression evaluation state trees.
! * Only "simple" expressions will actually use their slots, but it's
! * easiest to allocate a slot for each one anyway. All storage for the
! * state trees is in the eval_estate's "per-query" context.
*/
! estate->eval_exprtrees = (ExprState **)
! MemoryContextAllocZero(estate->eval_estate->es_query_cxt,
! estate->func->nexprs * sizeof(ExprState *));
}
/*
* plpgsql_destroy_econtext --- destroy function's econtext
*/
static void
plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
{
! /* This frees the exprcontext and the eval state trees as well: */
! FreeExecutorState(estate->eval_estate);
! estate->eval_estate = NULL;
estate->eval_econtext = NULL;
! estate->eval_exprtrees = NULL;
}
/*
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index ee49d71d23f9d53ab686f29b89de8e85379b8368..f6485653bb296c728a2bac382e97fa010c8ab44e 100644
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
*************** _PG_init(void)
*** 68,75 ****
EmitWarningsOnPlaceholders("plpgsql");
plpgsql_HashTableInit();
- RegisterXactCallback(plpgsql_xact_cb, NULL);
- RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
/* Set up a rendezvous point with optional instrumentation plugin */
plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
--- 68,73 ----
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 5b174c49b8bcac4658ffd54fc4493e43221533e7..bf67ca45f98f640d4475e94343b76083c13c1cfe 100644
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
*************** typedef struct PLpgSQL_expr
*** 200,205 ****
--- 200,206 ----
char *query;
SPIPlanPtr plan;
Bitmapset *paramnos; /* all dnos referenced by this query */
+ int expr_num; /* index in func's eval_exprtrees[] */
/* function containing this expr (not set until we first parse query) */
struct PLpgSQL_function *func;
*************** typedef struct PLpgSQL_expr
*** 211,224 ****
Expr *expr_simple_expr; /* NULL means not a simple expr */
int expr_simple_generation; /* plancache generation we checked */
Oid expr_simple_type; /* result type Oid, if simple */
-
- /*
- * if expr is simple AND prepared in current transaction,
- * expr_simple_state is valid. Test validity by seeing if expr_simple_lxid
- * matches current LXID.
- */
- ExprState *expr_simple_state;
- LocalTransactionId expr_simple_lxid;
} PLpgSQL_expr;
--- 212,217 ----
*************** typedef struct PLpgSQL_function
*** 675,680 ****
--- 668,675 ----
PLpgSQL_datum **datums;
PLpgSQL_stmt_block *action;
+ int nexprs; /* number of PLpgSQL_expr's in function */
+
/* these fields change when the function is used */
struct PLpgSQL_execstate *cur_estate;
unsigned long use_count;
*************** typedef struct PLpgSQL_execstate
*** 709,719 ****
int ndatums;
PLpgSQL_datum **datums;
/* temporary state for results from evaluation of query or expr */
SPITupleTable *eval_tuptable;
uint32 eval_processed;
Oid eval_lastoid;
- ExprContext *eval_econtext; /* for executing simple expressions */
PLpgSQL_expr *cur_expr; /* current query/expr being evaluated */
/* status information for error context reporting */
--- 704,718 ----
int ndatums;
PLpgSQL_datum **datums;
+ /* state for evaluation of "simple" expressions */
+ EState *eval_estate; /* EState for simple expressions */
+ ExprContext *eval_econtext; /* ExprContext for simple expressions */
+ ExprState **eval_exprtrees; /* array containing func->nexprs elements */
+
/* temporary state for results from evaluation of query or expr */
SPITupleTable *eval_tuptable;
uint32 eval_processed;
Oid eval_lastoid;
PLpgSQL_expr *cur_expr; /* current query/expr being evaluated */
/* status information for error context reporting */
*************** extern PLpgSQL_stmt_block *plpgsql_parse
*** 815,820 ****
--- 814,821 ----
extern int plpgsql_nDatums;
extern PLpgSQL_datum **plpgsql_Datums;
+ extern int plpgsql_nExprs;
+
extern char *plpgsql_error_funcname;
extern PLpgSQL_function *plpgsql_curr_compile;
*************** extern Datum plpgsql_exec_function(PLpgS
*** 875,883 ****
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);
- extern void plpgsql_xact_cb(XactEvent event, void *arg);
- extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
- SubTransactionId parentSubid, void *arg);
extern Oid exec_get_datum_type(PLpgSQL_execstate *estate,
PLpgSQL_datum *datum);
extern Oid exec_get_rec_fieldtype(PLpgSQL_rec *rec, const char *fieldname,
--- 876,881 ----