summaryrefslogtreecommitdiff
path: root/src/pl
diff options
context:
space:
mode:
Diffstat (limited to 'src/pl')
-rw-r--r--src/pl/plperl/plperl.c18
-rw-r--r--src/pl/plpgsql/src/pl_exec.c215
-rw-r--r--src/pl/plpython/plpython.c11
-rw-r--r--src/pl/tcl/pltcl.c17
4 files changed, 178 insertions, 83 deletions
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 8b5d4dc191..784e137976 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -165,7 +165,7 @@ typedef struct plperl_call_data
typedef struct plperl_query_desc
{
char qname[24];
- void *plan;
+ SPIPlanPtr plan;
int nargs;
Oid *argtypes;
FmgrInfo *arginfuncs;
@@ -2951,7 +2951,7 @@ plperl_spi_query(char *query)
PG_TRY();
{
- void *plan;
+ SPIPlanPtr plan;
Portal portal;
/* Make sure the query is validly encoded */
@@ -3118,7 +3118,7 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
plperl_query_desc *qdesc;
plperl_query_entry *hash_entry;
bool found;
- void *plan;
+ SPIPlanPtr plan;
int i;
MemoryContext oldcontext = CurrentMemoryContext;
@@ -3182,13 +3182,9 @@ plperl_spi_prepare(char *query, int argc, SV **argv)
* Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end).
************************************************************/
- qdesc->plan = SPI_saveplan(plan);
- if (qdesc->plan == NULL)
- elog(ERROR, "SPI_saveplan() failed: %s",
- SPI_result_code_string(SPI_result));
-
- /* Release the procCxt copy to avoid within-function memory leak */
- SPI_freeplan(plan);
+ if (SPI_keepplan(plan))
+ elog(ERROR, "SPI_keepplan() failed");
+ qdesc->plan = plan;
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
@@ -3516,7 +3512,7 @@ plperl_spi_query_prepared(char *query, int argc, SV **argv)
void
plperl_spi_freeplan(char *query)
{
- void *plan;
+ SPIPlanPtr plan;
plperl_query_desc *qdesc;
plperl_query_entry *hash_entry;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index de1aece92a..df785c9851 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -142,6 +142,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, int cursorOptions);
static bool exec_simple_check_node(Node *node);
static void exec_simple_check_plan(PLpgSQL_expr *expr);
+static void exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan);
static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
Datum *result,
@@ -2020,8 +2021,7 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
exec_prepare_plan(estate, query, curvar->cursor_options);
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, query);
@@ -2991,8 +2991,10 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
expr->query, SPI_result_code_string(SPI_result));
}
}
- expr->plan = SPI_saveplan(plan);
- SPI_freeplan(plan);
+ SPI_keepplan(plan);
+ expr->plan = plan;
+
+ /* Check to see if it's a simple expression */
exec_simple_check_plan(expr);
}
@@ -3011,6 +3013,11 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr = stmt->sqlstmt;
/*
+ * Set up ParamListInfo (hook function and possibly data values)
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
* On the first call for this statement generate the plan, and detect
* whether the statement is INSERT/UPDATE/DELETE
*/
@@ -3025,16 +3032,17 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
ListCell *l2;
- foreach(l2, plansource->plan->stmt_list)
+ Assert(plansource->is_valid);
+ foreach(l2, plansource->query_list)
{
- PlannedStmt *p = (PlannedStmt *) lfirst(l2);
+ Query *q = (Query *) lfirst(l2);
- if (IsA(p, PlannedStmt) &&
- p->canSetTag)
+ Assert(IsA(q, Query));
+ if (q->canSetTag)
{
- if (p->commandType == CMD_INSERT ||
- p->commandType == CMD_UPDATE ||
- p->commandType == CMD_DELETE)
+ if (q->commandType == CMD_INSERT ||
+ q->commandType == CMD_UPDATE ||
+ q->commandType == CMD_DELETE)
stmt->mod_stmt = true;
}
}
@@ -3042,12 +3050,6 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
- */
- paramLI = setup_param_list(estate, expr);
-
- /*
* If we have INTO, then we only need one row back ... but if we have INTO
* STRICT, ask for two rows, so that we can verify the statement returns
* only one. INSERT/UPDATE/DELETE are always treated strictly. Without
@@ -3520,8 +3522,7 @@ exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
}
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, query);
@@ -4613,8 +4614,7 @@ exec_run_select(PLpgSQL_execstate *estate,
exec_prepare_plan(estate, expr, 0);
/*
- * Set up ParamListInfo (note this is only carrying a hook function, not
- * any actual data values, at this point)
+ * Set up ParamListInfo (hook function and possibly data values)
*/
paramLI = setup_param_list(estate, expr);
@@ -4833,11 +4833,10 @@ loop_exit:
*
* It is possible though unlikely for a simple expression to become non-simple
* (consider for example redefining a trivial view). We must handle that for
- * correctness; fortunately it's normally inexpensive to do
- * RevalidateCachedPlan on a simple expression. We do not consider the other
- * direction (non-simple expression becoming simple) because we'll still give
- * correct results if that happens, and it's unlikely to be worth the cycles
- * to check.
+ * correctness; fortunately it's normally inexpensive to do GetCachedPlan on a
+ * simple expression. We do not consider the other direction (non-simple
+ * expression becoming simple) because we'll still give correct results if
+ * that happens, and it's unlikely to be worth the cycles to check.
*
* Note: if pass-by-reference, the result is in the eval_econtext's
* temporary memory context. It will be freed when exec_eval_cleanup
@@ -4873,17 +4872,21 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Revalidate cached plan, so that we will notice if it became stale. (We
- * also need to hold a refcount while using the plan.) Note that even if
- * replanning occurs, the length of plancache_list can't change, since it
- * is a property of the raw parsetree generated from the query text.
+ * need to hold a refcount while using the plan, anyway.) Note that even
+ * if replanning occurs, the length of plancache_list can't change, since
+ * it is a property of the raw parsetree generated from the query text.
*/
Assert(list_length(expr->plan->plancache_list) == 1);
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
- cplan = RevalidateCachedPlan(plansource, true);
+
+ /* Get the generic plan for the query */
+ cplan = GetCachedPlan(plansource, NULL, true);
+ Assert(cplan == plansource->gplan);
+
if (cplan->generation != expr->expr_simple_generation)
{
/* It got replanned ... is it still simple? */
- exec_simple_check_plan(expr);
+ exec_simple_recheck_plan(expr, cplan);
if (expr->expr_simple_expr == NULL)
{
/* Ooops, release refcount and fail */
@@ -4900,7 +4903,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* 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.)
+ * exec_simple_recheck_plan above.)
*/
if (expr->expr_simple_lxid != curlxid)
{
@@ -4931,9 +4934,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
* need to free it explicitly, since it will go away at the next reset of
* that context.
*
- * XXX think about avoiding repeated palloc's for param lists? It should
- * be possible --- this routine isn't re-entrant anymore.
- *
* Just for paranoia's sake, save and restore the prior value of
* estate->cur_expr, which setup_param_list() sets.
*/
@@ -4982,10 +4982,15 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/*
* Create a ParamListInfo to pass to SPI
*
- * The ParamListInfo array is initially all zeroes, in particular the
- * ptype values are all InvalidOid. This causes the executor to call the
- * paramFetch hook each time it wants a value. We thus evaluate only the
- * parameters actually demanded.
+ * We fill in the values for any expression parameters that are plain
+ * PLpgSQL_var datums; these are cheap and safe to evaluate, and by setting
+ * them with PARAM_FLAG_CONST flags, we allow the planner to use those values
+ * in custom plans. However, parameters that are not plain PLpgSQL_vars
+ * should not be evaluated here, because they could throw errors (for example
+ * "no such record field") and we do not want that to happen in a part of
+ * the expression that might never be evaluated at runtime. To handle those
+ * parameters, we set up a paramFetch hook for the executor to call when it
+ * wants a not-presupplied value.
*
* The result is a locally palloc'd array that should be pfree'd after use;
* but note it can be NULL.
@@ -4997,21 +5002,42 @@ setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
/*
* Could we re-use these arrays instead of palloc'ing a new one each time?
- * However, we'd have to zero the array each time anyway, since new values
- * might have been assigned to the variables.
+ * However, we'd have to re-fill the array each time anyway, since new
+ * values might have been assigned to the variables.
*/
if (estate->ndatums > 0)
{
- /* sizeof(ParamListInfoData) includes the first array element */
+ Bitmapset *tmpset;
+ int dno;
+
paramLI = (ParamListInfo)
- palloc0(sizeof(ParamListInfoData) +
- (estate->ndatums - 1) * sizeof(ParamExternData));
+ palloc0(offsetof(ParamListInfoData, params) +
+ estate->ndatums * sizeof(ParamExternData));
paramLI->paramFetch = plpgsql_param_fetch;
paramLI->paramFetchArg = (void *) estate;
paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
paramLI->parserSetupArg = (void *) expr;
paramLI->numParams = estate->ndatums;
+ /* Instantiate values for "safe" parameters of the expression */
+ tmpset = bms_copy(expr->paramnos);
+ while ((dno = bms_first_member(tmpset)) >= 0)
+ {
+ PLpgSQL_datum *datum = estate->datums[dno];
+
+ if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+ ParamExternData *prm = &paramLI->params[dno];
+
+ prm->value = var->value;
+ prm->isnull = var->isnull;
+ prm->pflags = PARAM_FLAG_CONST;
+ prm->ptype = var->datatype->typoid;
+ }
+ }
+ bms_free(tmpset);
+
/*
* Set up link to active expr where the hook functions can find it.
* Callers must save and restore cur_expr if there is any chance that
@@ -5628,30 +5654,113 @@ static void
exec_simple_check_plan(PLpgSQL_expr *expr)
{
CachedPlanSource *plansource;
- PlannedStmt *stmt;
- Plan *plan;
- TargetEntry *tle;
+ Query *query;
+ CachedPlan *cplan;
/*
* Initialize to "not simple", and remember the plan generation number we
- * last checked. (If the query produces more or less than one parsetree
+ * last checked. (If we don't get as far as obtaining a plan to check,
* we just leave expr_simple_generation set to 0.)
*/
expr->expr_simple_expr = NULL;
expr->expr_simple_generation = 0;
/*
- * 1. We can only evaluate queries that resulted in one single execution
- * plan
+ * We can only test queries that resulted in exactly one CachedPlanSource
*/
if (list_length(expr->plan->plancache_list) != 1)
return;
plansource = (CachedPlanSource *) linitial(expr->plan->plancache_list);
- expr->expr_simple_generation = plansource->generation;
- if (list_length(plansource->plan->stmt_list) != 1)
+
+ /*
+ * Do some checking on the analyzed-and-rewritten form of the query.
+ * These checks are basically redundant with the tests in
+ * exec_simple_recheck_plan, but the point is to avoid building a plan if
+ * possible. Since this function is only
+ * called immediately after creating the CachedPlanSource, we need not
+ * worry about the query being stale.
+ */
+
+ /*
+ * 1. There must be one single querytree.
+ */
+ if (list_length(plansource->query_list) != 1)
return;
+ query = (Query *) linitial(plansource->query_list);
- stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
+ /*
+ * 2. It must be a plain SELECT query without any input tables
+ */
+ if (!IsA(query, Query))
+ return;
+ if (query->commandType != CMD_SELECT || query->intoClause)
+ return;
+ if (query->rtable != NIL)
+ return;
+
+ /*
+ * 3. Can't have any subplans, aggregates, qual clauses either
+ */
+ if (query->hasAggs ||
+ query->hasWindowFuncs ||
+ query->hasSubLinks ||
+ query->hasForUpdate ||
+ query->cteList ||
+ query->jointree->quals ||
+ query->groupClause ||
+ query->havingQual ||
+ query->windowClause ||
+ query->distinctClause ||
+ query->sortClause ||
+ query->limitOffset ||
+ query->limitCount ||
+ query->setOperations)
+ return;
+
+ /*
+ * 4. The query must have a single attribute as result
+ */
+ if (list_length(query->targetList) != 1)
+ return;
+
+ /*
+ * OK, it seems worth constructing a plan for more careful checking.
+ */
+
+ /* Get the generic plan for the query */
+ cplan = GetCachedPlan(plansource, NULL, true);
+ Assert(cplan == plansource->gplan);
+
+ /* Share the remaining work with recheck code path */
+ exec_simple_recheck_plan(expr, cplan);
+
+ /* Release our plan refcount */
+ ReleaseCachedPlan(cplan, true);
+}
+
+/*
+ * exec_simple_recheck_plan --- check for simple plan once we have CachedPlan
+ */
+static void
+exec_simple_recheck_plan(PLpgSQL_expr *expr, CachedPlan *cplan)
+{
+ PlannedStmt *stmt;
+ Plan *plan;
+ TargetEntry *tle;
+
+ /*
+ * Initialize to "not simple", and remember the plan generation number we
+ * last checked.
+ */
+ expr->expr_simple_expr = NULL;
+ expr->expr_simple_generation = cplan->generation;
+
+ /*
+ * 1. There must be one single plantree
+ */
+ if (list_length(cplan->stmt_list) != 1)
+ return;
+ stmt = (PlannedStmt *) linitial(cplan->stmt_list);
/*
* 2. It must be a RESULT plan --> no scan's required
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 17c583ec35..93e8043284 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -287,7 +287,7 @@ typedef struct PLySubtransactionData
typedef struct PLyPlanObject
{
PyObject_HEAD
- void *plan; /* return of an SPI_saveplan */
+ SPIPlanPtr plan;
int nargs;
Oid *types;
Datum *values;
@@ -3327,7 +3327,6 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PyObject *list = NULL;
PyObject *volatile optr = NULL;
char *query;
- void *tmpplan;
volatile MemoryContext oldcontext;
volatile ResourceOwner oldowner;
volatile int nargs;
@@ -3431,12 +3430,8 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
SPI_result_code_string(SPI_result));
/* transfer plan from procCxt to topCxt */
- tmpplan = plan->plan;
- plan->plan = SPI_saveplan(tmpplan);
- SPI_freeplan(tmpplan);
- if (plan->plan == NULL)
- elog(ERROR, "SPI_saveplan failed: %s",
- SPI_result_code_string(SPI_result));
+ if (SPI_keepplan(plan->plan))
+ elog(ERROR, "SPI_keepplan failed");
/* Commit the inner transaction, return to outer xact context */
ReleaseCurrentSubTransaction();
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
index be8fe7a0f2..ecde90626b 100644
--- a/src/pl/tcl/pltcl.c
+++ b/src/pl/tcl/pltcl.c
@@ -128,7 +128,7 @@ typedef struct pltcl_proc_desc
typedef struct pltcl_query_desc
{
char qname[20];
- void *plan;
+ SPIPlanPtr plan;
int nargs;
Oid *argtypes;
FmgrInfo *arginfuncs;
@@ -2024,7 +2024,7 @@ pltcl_process_SPI_result(Tcl_Interp *interp,
* pltcl_SPI_prepare() - Builtin support for prepared plans
* The Tcl command SPI_prepare
* always saves the plan using
- * SPI_saveplan and returns a key for
+ * SPI_keepplan and returns a key for
* access. There is no chance to prepare
* and not save the plan currently.
**********************************************************************/
@@ -2035,7 +2035,6 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
int nargs;
CONST84 char **args;
pltcl_query_desc *qdesc;
- void *plan;
int i;
Tcl_HashEntry *hashent;
int hashnew;
@@ -2103,22 +2102,18 @@ pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
* Prepare the plan and check for errors
************************************************************/
UTF_BEGIN;
- plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes);
+ qdesc->plan = SPI_prepare(UTF_U2E(argv[1]), nargs, qdesc->argtypes);
UTF_END;
- if (plan == NULL)
+ if (qdesc->plan == NULL)
elog(ERROR, "SPI_prepare() failed");
/************************************************************
* Save the plan into permanent memory (right now it's in the
* SPI procCxt, which will go away at function end).
************************************************************/
- qdesc->plan = SPI_saveplan(plan);
- if (qdesc->plan == NULL)
- elog(ERROR, "SPI_saveplan() failed");
-
- /* Release the procCxt copy to avoid within-function memory leak */
- SPI_freeplan(plan);
+ if (SPI_keepplan(qdesc->plan))
+ elog(ERROR, "SPI_keepplan() failed");
pltcl_subtrans_commit(oldcontext, oldowner);
}