prepare_plans.patch
text/x-patch
Filename: prepare_plans.patch
Type: text/x-patch
Part: 0
*** a/src/pl/plpgsql/src/gram.y
--- b/src/pl/plpgsql/src/gram.y
***************
*** 293,298 **** static List *read_raise_options(void);
--- 293,300 ----
%token <keyword> K_NOT
%token <keyword> K_NOTICE
%token <keyword> K_NULL
+ %token <keyword> K_ON_DEMAND
+ %token <keyword> K_ON_START
%token <keyword> K_OPEN
%token <keyword> K_OPTION
%token <keyword> K_OR
***************
*** 300,305 **** static List *read_raise_options(void);
--- 302,308 ----
%token <keyword> K_PG_EXCEPTION_CONTEXT
%token <keyword> K_PG_EXCEPTION_DETAIL
%token <keyword> K_PG_EXCEPTION_HINT
+ %token <keyword> K_PREPARE_PLANS
%token <keyword> K_PRIOR
%token <keyword> K_QUERY
%token <keyword> K_RAISE
***************
*** 354,359 **** comp_option : '#' K_OPTION K_DUMP
--- 357,370 ----
{
plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN;
}
+ | '#' K_PREPARE_PLANS K_ON_DEMAND
+ {
+ plpgsql_curr_compile->prepare_plans_option = PLPGSQL_PREPARE_PLANS_ON_DEMAND;
+ }
+ | '#' K_PREPARE_PLANS K_ON_START
+ {
+ plpgsql_curr_compile->prepare_plans_option = PLPGSQL_PREPARE_PLANS_ON_START;
+ }
;
opt_semi :
***************
*** 2232,2241 **** unreserved_keyword :
--- 2243,2255 ----
| K_NEXT
| K_NO
| K_NOTICE
+ | K_ON_DEMAND
+ | K_ON_START
| K_OPTION
| K_PG_EXCEPTION_CONTEXT
| K_PG_EXCEPTION_DETAIL
| K_PG_EXCEPTION_HINT
+ | K_PREPARE_PLANS
| K_PRIOR
| K_QUERY
| K_RELATIVE
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 354,359 **** do_compile(FunctionCallInfo fcinfo,
--- 354,360 ----
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
+ function->prepare_plans_option = PLPGSQL_PREPARE_PLANS_UNDEF;
/*
* Initialize the compiler, particularly the namespace stack. The
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 210,215 **** static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
--- 210,217 ----
PLpgSQL_expr *dynquery, List *params,
const char *portalname, int cursorOptions);
+ static bool expr_prepare_plan(PLpgSQL_stmt *stmt,
+ PLpgSQL_expr *expr, void *context);
/* ----------
* plpgsql_exec_function Called by the call handler for
***************
*** 310,315 **** plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo)
--- 312,375 ----
exec_set_found(&estate, false);
/*
+ * Prepare plans for all embeded SQL when it's requested
+ */
+ if ((func->prepare_plans_option == PLPGSQL_PREPARE_PLANS_ON_START) ||
+ (func->prepare_plans_option == PLPGSQL_PREPARE_PLANS_UNDEF &&
+ plpgsql_prepare_plans == PLPGSQL_PREPARE_PLANS_ON_START))
+ {
+ int i;
+ PLpgSQL_rec *recvars;
+
+ /*
+ * walker can chanhe a type to REC variable. This can be a unwanted
+ * change of behave - so we save all REC variables before walker's run
+ * and later we revert to back all values.
+ */
+ recvars = palloc(sizeof(PLpgSQL_rec) * estate.ndatums);
+ for (i = 0; i < estate.ndatums; i++)
+ {
+ if (estate.datums[i]->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[i];
+
+ recvars[i].tup = rec->tup;
+ recvars[i].tupdesc = rec->tupdesc;
+ recvars[i].freetup = rec->freetup;
+ recvars[i].freetupdesc = rec->freetupdesc;
+
+ /* don't release a original record */
+ rec->freetup = false;
+ rec->freetupdesc = false;
+ }
+ }
+
+ plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
+ expr_prepare_plan,
+ &estate);
+ for (i = 0; i < estate.ndatums; i++)
+ {
+ if (estate.datums[i]->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[i];
+
+ if (rec->freetup)
+ heap_freetuple(rec->tup);
+
+ if (rec->freetupdesc)
+ FreeTupleDesc(rec->tupdesc);
+
+ rec->tup = recvars[i].tup;
+ rec->tupdesc = recvars[i].tupdesc;
+ rec->freetup = recvars[i].freetup;
+ rec->freetupdesc = recvars[i].freetupdesc;
+ }
+ }
+
+ pfree(recvars);
+ }
+
+ /*
* Let the instrumentation plugin peek at this function
*/
if (*plugin_ptr && (*plugin_ptr)->func_beg)
***************
*** 676,681 **** plpgsql_exec_trigger(PLpgSQL_function *func,
--- 736,800 ----
exec_set_found(&estate, false);
/*
+ * Prepare plans for all embeded SQL when it's requested
+ */
+ if ((func->prepare_plans_option == PLPGSQL_PREPARE_PLANS_ON_START) ||
+ (func->prepare_plans_option == PLPGSQL_PREPARE_PLANS_UNDEF &&
+ plpgsql_prepare_plans == PLPGSQL_PREPARE_PLANS_ON_START))
+ {
+ int i;
+ PLpgSQL_rec *recvars;
+
+ /*
+ * walker can chanhe a type to REC variable. This can be a unwanted
+ * change of behave - so we save all REC variables before walker's run
+ * and later we revert to back all values.
+ */
+ recvars = palloc(sizeof(PLpgSQL_rec) * estate.ndatums);
+ for (i = 0; i < estate.ndatums; i++)
+ {
+ if (estate.datums[i]->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[i];
+
+ recvars[i].tup = rec->tup;
+ recvars[i].tupdesc = rec->tupdesc;
+ recvars[i].freetup = rec->freetup;
+ recvars[i].freetupdesc = rec->freetupdesc;
+
+ /* don't release a original record */
+ rec->freetup = false;
+ rec->freetupdesc = false;
+ }
+ }
+
+ plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
+ expr_prepare_plan,
+ &estate);
+ for (i = 0; i < estate.ndatums; i++)
+ {
+ if (estate.datums[i]->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[i];
+
+ if (rec->freetup)
+ heap_freetuple(rec->tup);
+
+ if (rec->freetupdesc)
+ FreeTupleDesc(rec->tupdesc);
+
+ rec->tup = recvars[i].tup;
+ rec->tupdesc = recvars[i].tupdesc;
+ rec->freetup = recvars[i].freetup;
+ rec->freetupdesc = recvars[i].freetupdesc;
+ }
+ }
+
+ pfree(recvars);
+
+ }
+
+ /*
* Let the instrumentation plugin peek at this function
*/
if (*plugin_ptr && (*plugin_ptr)->func_beg)
***************
*** 6028,6030 **** exec_dynquery_with_params(PLpgSQL_execstate *estate,
--- 6147,6610 ----
return portal;
}
+
+ /*
+ * Sometime we must initialize a unknown record variable with NULL
+ * of type that is derived from some plan. This is necessary for later
+ * using a rec variable. Last parameter 'use_element_type' is true, when
+ * we would to assign a element type of result array.
+ *
+ */
+ static void
+ set_rectype_for_query(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt *stmt,
+ PLpgSQL_rec *rec,
+ PLpgSQL_expr *query,
+ bool use_element_type)
+ {
+ bool *nulls;
+ HeapTuple tup;
+ CachedPlanSource *plansource = NULL;
+ const char *err_text = estate->err_text;
+
+ estate->err_text = NULL;
+ estate->err_stmt = stmt;
+
+ if (rec->freetup)
+ heap_freetuple(rec->tup);
+
+ if (rec->freetupdesc)
+ FreeTupleDesc(rec->tupdesc);
+
+ if (query->plan != NULL)
+ {
+ SPIPlanPtr plan = query->plan;
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ elog(ERROR, "cached plan is not valid plan");
+
+ if (list_length(plan->plancache_list) != 1)
+ elog(ERROR, "plan is not single execution plan");
+
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+
+ rec->tupdesc = CreateTupleDescCopy(plansource->resultDesc);
+ rec->freetupdesc = true;
+ }
+ else
+ elog(ERROR, "there are no plan for query: \"%s\"",
+ query->query);
+
+ /*
+ * try to get a element type, when result is a array (used with FOREACH ARRAY stmt)
+ */
+ if (use_element_type)
+ {
+ Oid elemtype;
+ TupleDesc tupdesc;
+
+ /* result should be a array */
+ if (rec->tupdesc->natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("query \"%s\" returned %d column",
+ "query \"%s\" returned %d columns",
+ rec->tupdesc->natts,
+ query->query,
+ rec->tupdesc->natts)));
+
+ /* check the type of the expression - must be an array */
+ elemtype = get_element_type(rec->tupdesc->attrs[0]->atttypid);
+ if (!OidIsValid(elemtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH expression must yield an array, not type %s",
+ format_type_be(rec->tupdesc->attrs[0]->atttypid))));
+
+ /* we can't know typmod now */
+ tupdesc = lookup_rowtype_tupdesc_noerror(elemtype, -1, true);
+ if (tupdesc != NULL)
+ {
+ if (rec->freetupdesc)
+ FreeTupleDesc(rec->tupdesc);
+ rec->tupdesc = CreateTupleDescCopy(tupdesc);
+ rec->freetupdesc = true;
+ ReleaseTupleDesc(tupdesc);
+ }
+ else
+ elog(ERROR, "cannot to identify real type for record type variable");
+ }
+
+ /*
+ * When record is assigned to composite type, then
+ * we should to unpack composite type.
+ */
+ if (rec->tupdesc->tdtypeid == RECORDOID &&
+ rec->tupdesc->tdtypmod == -1 &&
+ rec->tupdesc->natts == 1 &&
+ stmt->cmd_type == PLPGSQL_STMT_ASSIGN)
+ {
+ TupleDesc tupdesc;
+
+ tupdesc = lookup_rowtype_tupdesc_noerror(rec->tupdesc->attrs[0]->atttypid,
+ rec->tupdesc->attrs[0]->atttypmod,
+ true);
+ if (tupdesc != NULL)
+ {
+ if (rec->freetupdesc)
+ FreeTupleDesc(rec->tupdesc);
+ rec->tupdesc = CreateTupleDescCopy(tupdesc);
+ rec->freetupdesc = true;
+ ReleaseTupleDesc(tupdesc);
+ }
+ }
+
+ /*
+ * When returned tupdesc contains only
+ * unpined record: rec := func_with_out_parameters(). IN this case
+ * we must to dig more deep - we have to find oid of function and
+ * get their parameters,
+ *
+ * This is support for assign statement
+ * recvar := func_with_out_parameters(..)
+ */
+ if (rec->tupdesc->tdtypeid == RECORDOID &&
+ rec->tupdesc->tdtypmod == -1 &&
+ rec->tupdesc->natts == 1 &&
+ rec->tupdesc->attrs[0]->atttypid == RECORDOID &&
+ rec->tupdesc->attrs[0]->atttypmod == -1)
+ {
+ PlannedStmt *_stmt;
+ Plan *_plan;
+ TargetEntry *tle;
+
+ /*
+ * When tupdesc is related to unpined record, we will try
+ * to check plan if it is just function call and if it is
+ * then we can try to derive a tupledes from function's
+ * description.
+ */
+ _stmt = (PlannedStmt *) linitial(plansource->plan->stmt_list);
+
+ if (IsA(_stmt, PlannedStmt) && _stmt->commandType == CMD_SELECT)
+ {
+ _plan = _stmt->planTree;
+ if (IsA(_plan, Result) && list_length(_plan->targetlist) == 1)
+ {
+ tle = (TargetEntry *) linitial(_plan->targetlist);
+ if (((Node *) tle->expr)->type == T_FuncExpr)
+ {
+ FuncExpr *fn = (FuncExpr *) tle->expr;
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+ TupleDesc rd;
+ Oid rt;
+
+ fmgr_info(fn->funcid, &flinfo);
+ flinfo.fn_expr = (Node *) fn;
+ fcinfo.flinfo = &flinfo;
+
+ get_call_result_type(&fcinfo, &rt, &rd);
+ if (rd == NULL)
+ elog(ERROR, "function does not return composite type is not possible to identify composite type");
+
+ FreeTupleDesc(rec->tupdesc);
+ BlessTupleDesc(rd);
+
+ rec->tupdesc = rd;
+ }
+ }
+ }
+ }
+
+ /* last recheck */
+ if (rec->tupdesc->tdtypeid == RECORDOID &&
+ rec->tupdesc->tdtypmod == -1 &&
+ rec->tupdesc->natts == 1 &&
+ rec->tupdesc->attrs[0]->atttypid == RECORDOID &&
+ rec->tupdesc->attrs[0]->atttypmod == -1)
+ elog(ERROR, "cannot to identify real type for record type variable");
+
+ /* initialize rec by NULLs - variable should be valid */
+ nulls = (bool *) palloc(rec->tupdesc->natts * sizeof(bool));
+ memset(nulls, true, rec->tupdesc->natts * sizeof(bool));
+
+ tup = heap_form_tuple(rec->tupdesc, NULL, nulls);
+ if (HeapTupleIsValid(tup))
+ {
+ rec->tup = tup;
+ rec->freetup = true;
+ }
+ else
+ {
+ rec->tup = NULL;
+ rec->freetup = false;
+ }
+
+ estate->err_text = err_text;
+ }
+
+ static bool
+ expr_prepare_plan(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context)
+ {
+ PLpgSQL_execstate *estate = (PLpgSQL_execstate *) context;
+ int cursorOptions = 0;
+ const char *err_text = estate->err_text;
+
+ /* overwrite a estate variables */
+ estate->err_text = NULL;
+ estate->err_stmt = stmt;
+
+ if (expr == NULL)
+ return false;
+
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_OPEN:
+ {
+ PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
+ PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_open->curvar];
+
+ cursorOptions = curvar->cursor_options;
+ }
+ break;
+
+ case PLPGSQL_STMT_FORC:
+ {
+ PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ PLpgSQL_var *curvar = (PLpgSQL_var *) estate->datums[stmt_forc->curvar];
+
+ /*
+ * change a cursorOption only whenn this call is related to
+ * curvar->cursor_explicit_expr
+ */
+ if (curvar->cursor_explicit_expr == expr)
+ cursorOptions = curvar->cursor_options;
+ }
+ break;
+ }
+
+ /*
+ * If first time through, create a plan for this expression.
+ */
+ if (expr->plan == NULL)
+ {
+ exec_prepare_plan(estate, expr, cursorOptions);
+ }
+
+ /*
+ * very common practic in PLpgSQL is is using a record type. But any using of
+ * untyped record breaks a check. A solution is an prediction of record type based
+ * on plans - a following switch covers all PLpgSQL statements where a record
+ * variable can be assigned.
+ *
+ * when record is target of dynamic SQL statement, then raise exception
+ *
+ */
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_ASSIGN:
+ {
+ PLpgSQL_stmt_assign *stmt_assign = (PLpgSQL_stmt_assign *) stmt;
+ PLpgSQL_datum *target = (estate->datums[stmt_assign->varno]);
+
+ if (target->dtype == PLPGSQL_DTYPE_REC)
+ {
+ set_rectype_for_query(estate, stmt,
+ (PLpgSQL_rec *) target,
+ stmt_assign->expr,
+ false);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_EXECSQL:
+ {
+ PLpgSQL_stmt_execsql *stmt_execsql = (PLpgSQL_stmt_execsql *) stmt;
+ ListCell *l;
+
+ stmt_execsql->mod_stmt = false;
+ foreach(l, expr->plan->plancache_list)
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
+ ListCell *l2;
+
+ foreach(l2, plansource->plan->stmt_list)
+ {
+ PlannedStmt *p = (PlannedStmt *) lfirst(l2);
+
+ if (IsA(p, PlannedStmt) &&
+ p->canSetTag)
+ {
+ if (p->commandType == CMD_INSERT ||
+ p->commandType == CMD_UPDATE ||
+ p->commandType == CMD_DELETE)
+ stmt_execsql->mod_stmt = true;
+ }
+ }
+ }
+
+ if (stmt_execsql->rec != NULL)
+ {
+ set_rectype_for_query(estate, stmt,
+ (PLpgSQL_rec *) (estate->datums[stmt_execsql->rec->dno]),
+ stmt_execsql->sqlstmt,
+ false);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_FETCH:
+ {
+ PLpgSQL_stmt_fetch *stmt_fetch = (PLpgSQL_stmt_fetch *) stmt;
+
+ /* fetch can not determinate a record datatype for refcursors */
+ if (stmt_fetch->rec != NULL)
+ {
+ PLpgSQL_var *curvar = (PLpgSQL_var *)( estate->datums[stmt_fetch->curvar]);
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_fetch->rec->dno]);
+
+ if (curvar->cursor_explicit_expr == NULL)
+ elog(ERROR, "cannot to determinate record type for refcursor");
+
+ set_rectype_for_query(estate, stmt,
+ rec,
+ curvar->cursor_explicit_expr,
+ false);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_FORS:
+ {
+ PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
+
+ if (stmt_fors->rec != NULL)
+ {
+ set_rectype_for_query(estate, stmt,
+ (PLpgSQL_rec *) (estate->datums[stmt_fors->rec->dno]),
+ stmt_fors->query,
+ false);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_FORC:
+ {
+ PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
+ PLpgSQL_var *curvar = (PLpgSQL_var *) (estate->datums[stmt_forc->curvar]);
+
+ if (stmt_forc->rec != NULL && curvar->cursor_explicit_expr == expr)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) (estate->datums[stmt_forc->rec->dno]);
+
+ set_rectype_for_query(estate, stmt,
+ rec,
+ curvar->cursor_explicit_expr,
+ false);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_FOREACH_A:
+ {
+ PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
+ PLpgSQL_datum *loop_var = estate->datums[stmt_foreach_a->varno];
+
+ if (loop_var->dtype == PLPGSQL_DTYPE_REC)
+ {
+ set_rectype_for_query(estate, stmt,
+ (PLpgSQL_rec *) loop_var,
+ stmt_foreach_a->expr,
+ true);
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_CASE:
+ {
+ PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
+ TupleDesc tupdesc;
+ Oid result_oid;
+
+ /*
+ * this is special case - a result type of expression should to
+ * overwrite a expected int datatype.
+ */
+ if (stmt_case->t_expr == expr)
+ {
+ CachedPlanSource *plansource = NULL;
+ const char *err_text = estate->err_text;
+
+ estate->err_text = NULL;
+ estate->err_stmt = stmt;
+
+
+ if (expr->plan != NULL)
+ {
+ SPIPlanPtr plan = expr->plan;
+ PLpgSQL_var *t_var = (PLpgSQL_var *) estate->datums[stmt_case->t_varno];
+
+ if (plan == NULL || plan->magic != _SPI_PLAN_MAGIC)
+ elog(ERROR, "cached plan is not valid plan");
+
+ if (list_length(plan->plancache_list) != 1)
+ elog(ERROR, "plan is not single execution plan");
+
+ plansource = (CachedPlanSource *) linitial(plan->plancache_list);
+ tupdesc = CreateTupleDescCopy(plansource->resultDesc);
+
+ if (tupdesc->natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("query \"%s\" returned %d column",
+ "query \"%s\" returned %d columns",
+ tupdesc->natts,
+ expr->query,
+ tupdesc->natts)));
+
+ result_oid = tupdesc->attrs[0]->atttypid;
+
+ /*
+ * When expected datatype is different from real, change it. Note that
+ * what we're modifying here is an execution copy of the datum, so
+ * this doesn't affect the originally stored function parse tree.
+ */
+ if (t_var->datatype->typoid != result_oid)
+ t_var->datatype = plpgsql_build_datatype(result_oid,
+ -1,
+ estate->func->fn_input_collation);
+ FreeTupleDesc(tupdesc);
+ }
+ else
+ elog(ERROR, "there are no plan for query: \"%s\"",
+ expr->query);
+
+ estate->err_text = err_text;
+ }
+ }
+ break;
+
+ case PLPGSQL_STMT_DYNEXECUTE:
+ {
+ PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
+
+ if (stmt_dynexecute->into && stmt_dynexecute->rec != NULL)
+ elog(ERROR, "cannot to determine a result of dynamic SQL");
+ }
+ break;
+
+ case PLPGSQL_STMT_DYNFORS:
+ {
+ PLpgSQL_stmt_dynfors *stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
+
+ if (stmt_dynfors->rec != NULL)
+ elog(ERROR, "cannot to determinate a result of dynamic SQL");
+ }
+ break;
+ }
+
+ estate->err_text = err_text;
+
+ return false;
+ }
*** a/src/pl/plpgsql/src/pl_funcs.c
--- b/src/pl/plpgsql/src/pl_funcs.c
***************
*** 292,681 **** plpgsql_getdiag_kindname(int kind)
return "unknown";
}
!
! /**********************************************************************
! * Release memory when a PL/pgSQL function is no longer needed
*
! * The code for recursing through the function tree is really only
! * needed to locate PLpgSQL_expr nodes, which may contain references
! * to saved SPI Plans that must be freed. The function tree itself,
! * along with subsidiary data, is freed in one swoop by freeing the
! * function's permanent memory context.
! **********************************************************************/
! static void free_stmt(PLpgSQL_stmt *stmt);
! static void free_block(PLpgSQL_stmt_block *block);
! static void free_assign(PLpgSQL_stmt_assign *stmt);
! static void free_if(PLpgSQL_stmt_if *stmt);
! static void free_case(PLpgSQL_stmt_case *stmt);
! static void free_loop(PLpgSQL_stmt_loop *stmt);
! static void free_while(PLpgSQL_stmt_while *stmt);
! static void free_fori(PLpgSQL_stmt_fori *stmt);
! static void free_fors(PLpgSQL_stmt_fors *stmt);
! static void free_forc(PLpgSQL_stmt_forc *stmt);
! static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
! static void free_exit(PLpgSQL_stmt_exit *stmt);
! static void free_return(PLpgSQL_stmt_return *stmt);
! static void free_return_next(PLpgSQL_stmt_return_next *stmt);
! static void free_return_query(PLpgSQL_stmt_return_query *stmt);
! static void free_raise(PLpgSQL_stmt_raise *stmt);
! static void free_execsql(PLpgSQL_stmt_execsql *stmt);
! static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
! static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
! static void free_getdiag(PLpgSQL_stmt_getdiag *stmt);
! static void free_open(PLpgSQL_stmt_open *stmt);
! static void free_fetch(PLpgSQL_stmt_fetch *stmt);
! static void free_close(PLpgSQL_stmt_close *stmt);
! static void free_perform(PLpgSQL_stmt_perform *stmt);
! static void free_expr(PLpgSQL_expr *expr);
! static void
! free_stmt(PLpgSQL_stmt *stmt)
{
switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
! free_block((PLpgSQL_stmt_block *) stmt);
! break;
case PLPGSQL_STMT_ASSIGN:
! free_assign((PLpgSQL_stmt_assign *) stmt);
! break;
case PLPGSQL_STMT_IF:
! free_if((PLpgSQL_stmt_if *) stmt);
! break;
case PLPGSQL_STMT_CASE:
! free_case((PLpgSQL_stmt_case *) stmt);
! break;
case PLPGSQL_STMT_LOOP:
! free_loop((PLpgSQL_stmt_loop *) stmt);
! break;
case PLPGSQL_STMT_WHILE:
! free_while((PLpgSQL_stmt_while *) stmt);
! break;
case PLPGSQL_STMT_FORI:
! free_fori((PLpgSQL_stmt_fori *) stmt);
! break;
case PLPGSQL_STMT_FORS:
! free_fors((PLpgSQL_stmt_fors *) stmt);
! break;
case PLPGSQL_STMT_FORC:
! free_forc((PLpgSQL_stmt_forc *) stmt);
! break;
! case PLPGSQL_STMT_FOREACH_A:
! free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
! break;
! case PLPGSQL_STMT_EXIT:
! free_exit((PLpgSQL_stmt_exit *) stmt);
! break;
! case PLPGSQL_STMT_RETURN:
! free_return((PLpgSQL_stmt_return *) stmt);
! break;
! case PLPGSQL_STMT_RETURN_NEXT:
! free_return_next((PLpgSQL_stmt_return_next *) stmt);
! break;
! case PLPGSQL_STMT_RETURN_QUERY:
! free_return_query((PLpgSQL_stmt_return_query *) stmt);
! break;
! case PLPGSQL_STMT_RAISE:
! free_raise((PLpgSQL_stmt_raise *) stmt);
! break;
! case PLPGSQL_STMT_EXECSQL:
! free_execsql((PLpgSQL_stmt_execsql *) stmt);
! break;
! case PLPGSQL_STMT_DYNEXECUTE:
! free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
! break;
case PLPGSQL_STMT_DYNFORS:
! free_dynfors((PLpgSQL_stmt_dynfors *) stmt);
! break;
! case PLPGSQL_STMT_GETDIAG:
! free_getdiag((PLpgSQL_stmt_getdiag *) stmt);
! break;
! case PLPGSQL_STMT_OPEN:
! free_open((PLpgSQL_stmt_open *) stmt);
! break;
! case PLPGSQL_STMT_FETCH:
! free_fetch((PLpgSQL_stmt_fetch *) stmt);
! break;
! case PLPGSQL_STMT_CLOSE:
! free_close((PLpgSQL_stmt_close *) stmt);
! break;
! case PLPGSQL_STMT_PERFORM:
! free_perform((PLpgSQL_stmt_perform *) stmt);
! break;
! default:
! elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
! break;
! }
! }
! static void
! free_stmts(List *stmts)
! {
! ListCell *s;
! foreach(s, stmts)
! {
! free_stmt((PLpgSQL_stmt *) lfirst(s));
! }
! }
! static void
! free_block(PLpgSQL_stmt_block *block)
! {
! free_stmts(block->body);
! if (block->exceptions)
! {
! ListCell *e;
! foreach(e, block->exceptions->exc_list)
! {
! PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
! free_stmts(exc->action);
! }
! }
! }
! static void
! free_assign(PLpgSQL_stmt_assign *stmt)
! {
! free_expr(stmt->expr);
! }
! static void
! free_if(PLpgSQL_stmt_if *stmt)
! {
! free_expr(stmt->cond);
! free_stmts(stmt->true_body);
! free_stmts(stmt->false_body);
! }
! static void
! free_case(PLpgSQL_stmt_case *stmt)
! {
! ListCell *l;
! free_expr(stmt->t_expr);
! foreach(l, stmt->case_when_list)
! {
! PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
! free_expr(cwt->expr);
! free_stmts(cwt->stmts);
! }
! free_stmts(stmt->else_stmts);
! }
! static void
! free_loop(PLpgSQL_stmt_loop *stmt)
! {
! free_stmts(stmt->body);
! }
! static void
! free_while(PLpgSQL_stmt_while *stmt)
! {
! free_expr(stmt->cond);
! free_stmts(stmt->body);
! }
! static void
! free_fori(PLpgSQL_stmt_fori *stmt)
! {
! free_expr(stmt->lower);
! free_expr(stmt->upper);
! free_expr(stmt->step);
! free_stmts(stmt->body);
! }
! static void
! free_fors(PLpgSQL_stmt_fors *stmt)
! {
! free_stmts(stmt->body);
! free_expr(stmt->query);
! }
! static void
! free_forc(PLpgSQL_stmt_forc *stmt)
! {
! free_stmts(stmt->body);
! free_expr(stmt->argquery);
! }
! static void
! free_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
! {
! free_expr(stmt->expr);
! free_stmts(stmt->body);
! }
! static void
! free_open(PLpgSQL_stmt_open *stmt)
! {
! ListCell *lc;
! free_expr(stmt->argquery);
! free_expr(stmt->query);
! free_expr(stmt->dynquery);
! foreach(lc, stmt->params)
! {
! free_expr((PLpgSQL_expr *) lfirst(lc));
! }
! }
! static void
! free_fetch(PLpgSQL_stmt_fetch *stmt)
! {
! free_expr(stmt->expr);
! }
! static void
! free_close(PLpgSQL_stmt_close *stmt)
! {
! }
! static void
! free_perform(PLpgSQL_stmt_perform *stmt)
! {
! free_expr(stmt->expr);
! }
! static void
! free_exit(PLpgSQL_stmt_exit *stmt)
! {
! free_expr(stmt->cond);
! }
! static void
! free_return(PLpgSQL_stmt_return *stmt)
! {
! free_expr(stmt->expr);
! }
! static void
! free_return_next(PLpgSQL_stmt_return_next *stmt)
! {
! free_expr(stmt->expr);
! }
! static void
! free_return_query(PLpgSQL_stmt_return_query *stmt)
! {
! ListCell *lc;
! free_expr(stmt->query);
! free_expr(stmt->dynquery);
! foreach(lc, stmt->params)
! {
! free_expr((PLpgSQL_expr *) lfirst(lc));
! }
! }
! static void
! free_raise(PLpgSQL_stmt_raise *stmt)
! {
! ListCell *lc;
! foreach(lc, stmt->params)
! {
! free_expr((PLpgSQL_expr *) lfirst(lc));
! }
! foreach(lc, stmt->options)
! {
! PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
! free_expr(opt->expr);
! }
! }
! static void
! free_execsql(PLpgSQL_stmt_execsql *stmt)
! {
! free_expr(stmt->sqlstmt);
! }
! static void
! free_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
! {
! ListCell *lc;
! free_expr(stmt->query);
! foreach(lc, stmt->params)
! {
! free_expr((PLpgSQL_expr *) lfirst(lc));
! }
! }
! static void
! free_dynfors(PLpgSQL_stmt_dynfors *stmt)
! {
! ListCell *lc;
! free_stmts(stmt->body);
! free_expr(stmt->query);
! foreach(lc, stmt->params)
! {
! free_expr((PLpgSQL_expr *) lfirst(lc));
}
}
! static void
! free_getdiag(PLpgSQL_stmt_getdiag *stmt)
! {
! }
! static void
! free_expr(PLpgSQL_expr *expr)
{
if (expr && expr->plan)
{
SPI_freeplan(expr->plan);
expr->plan = NULL;
}
}
void
plpgsql_free_function_memory(PLpgSQL_function *func)
{
- int i;
-
/* Better not call this on an in-use function */
Assert(func->use_count == 0);
! /* Release plans associated with variable declarations */
! for (i = 0; i < func->ndatums; i++)
! {
! PLpgSQL_datum *d = func->datums[i];
!
! switch (d->dtype)
! {
! case PLPGSQL_DTYPE_VAR:
! {
! PLpgSQL_var *var = (PLpgSQL_var *) d;
- free_expr(var->default_val);
- free_expr(var->cursor_explicit_expr);
- }
- break;
- case PLPGSQL_DTYPE_ROW:
- break;
- case PLPGSQL_DTYPE_REC:
- break;
- case PLPGSQL_DTYPE_RECFIELD:
- break;
- case PLPGSQL_DTYPE_ARRAYELEM:
- free_expr(((PLpgSQL_arrayelem *) d)->subscript);
- break;
- default:
- elog(ERROR, "unrecognized data type: %d", d->dtype);
- }
- }
func->ndatums = 0;
-
- /* Release plans in statement tree */
- if (func->action)
- free_block(func->action);
func->action = NULL;
/*
--- 292,653 ----
return "unknown";
}
! /*
! * call a plpgsql_expr_walker for any statement in list
*
! */
! static bool
! plpgsql_expr_walker_list(PLpgSQL_function *func, List *stmts,
! bool (*expr_walker)(),
! void *context)
! {
! ListCell *lc;
+ foreach(lc, stmts)
+ {
+ PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(lc);
! if (plpgsql_expr_walker(func, stmt, expr_walker, context))
! return true;
! }
! return false;
! }
!
! /*
! * walk over all expressions inside statements tree
! *
! * stmt_walker is function called for every stmt and should be NULL
! *
! */
! bool
! plpgsql_expr_walker(PLpgSQL_function *func,
! PLpgSQL_stmt *stmt,
! bool (*expr_walker)(),
! void *context)
{
+ ListCell *l;
+
+ if (stmt == NULL)
+ return false;
+
switch ((enum PLpgSQL_stmt_types) stmt->cmd_type)
{
case PLPGSQL_STMT_BLOCK:
! {
! PLpgSQL_stmt_block *stmt_block = (PLpgSQL_stmt_block *) stmt;
! int i;
! PLpgSQL_datum *d;
!
! for (i = 0; i < stmt_block->n_initvars; i++)
! {
! d = func->datums[stmt_block->initvarnos[i]];
!
! switch(d->dtype)
! {
! case PLPGSQL_DTYPE_VAR:
! {
! PLpgSQL_var *var = (PLpgSQL_var *) d;
!
! if (expr_walker(stmt, var->default_val, context))
! return true;
!
! /*
! * theoretically there is place for walk over var->cursor_explicit_expr,
! * but we would not to call process too early. In this moment a some
! * record parameters should be unknown. So we will wait on better moment
! *
! * if (expr_walker(stmt, var->cursor_explicit_expr, context))
! * return true;
! */
! }
! break;
! case PLPGSQL_DTYPE_ROW:
! case PLPGSQL_DTYPE_REC:
! case PLPGSQL_DTYPE_RECFIELD:
! break;
! case PLPGSQL_DTYPE_ARRAYELEM:
! if (expr_walker(stmt, ((PLpgSQL_arrayelem *) d)->subscript, context))
! return true;
! default:
! elog(ERROR, "unrecognized data type: %d", d->dtype);
! }
! }
!
! if (plpgsql_expr_walker_list(func, stmt_block->body, expr_walker, context))
! return true;
!
! if (stmt_block->exceptions)
! {
! foreach(l, stmt_block->exceptions->exc_list)
! {
! if (plpgsql_expr_walker_list(func, ((PLpgSQL_exception *) lfirst(l))->action,
! expr_walker,
! context))
! return true;
! }
! }
!
! return false;
! }
!
case PLPGSQL_STMT_ASSIGN:
! return expr_walker(stmt, ((PLpgSQL_stmt_assign *) stmt)->expr, context);
!
case PLPGSQL_STMT_IF:
! {
! PLpgSQL_stmt_if *stmt_if = (PLpgSQL_stmt_if *) stmt;
!
! if (expr_walker(stmt, stmt_if->cond, context))
! return true;
!
! if (plpgsql_expr_walker_list(func, stmt_if->true_body, expr_walker, context))
! return true;
!
! return plpgsql_expr_walker_list(func, stmt_if->false_body, expr_walker, context);
! }
!
case PLPGSQL_STMT_CASE:
! {
! PLpgSQL_stmt_case *stmt_case = (PLpgSQL_stmt_case *) stmt;
!
! if (expr_walker(stmt, stmt_case->t_expr, context))
! return true;
!
! foreach(l, stmt_case->case_when_list)
! {
! PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
!
! if (expr_walker(stmt, cwt->expr, context))
! return true;
!
! if (plpgsql_expr_walker_list(func, cwt->stmts, expr_walker, context))
! return true;
! }
!
! return plpgsql_expr_walker_list(func, stmt_case->else_stmts, expr_walker, context);
! }
!
case PLPGSQL_STMT_LOOP:
! return plpgsql_expr_walker_list(func, ((PLpgSQL_stmt_loop *) stmt)->body, expr_walker, context);
!
case PLPGSQL_STMT_WHILE:
! {
! PLpgSQL_stmt_while *stmt_while = (PLpgSQL_stmt_while *) stmt;
!
! if (expr_walker(stmt, stmt_while->cond, context))
! return true;
!
! return plpgsql_expr_walker_list(func, stmt_while->body, expr_walker, context);
! }
!
case PLPGSQL_STMT_FORI:
! {
! PLpgSQL_stmt_fori *stmt_fori = (PLpgSQL_stmt_fori *) stmt;
!
! if (expr_walker(stmt, stmt_fori->lower, context))
! return true;
!
! if (expr_walker(stmt, stmt_fori->upper, context))
! return true;
!
! if (expr_walker(stmt, stmt_fori->step, context))
! return true;
!
! return plpgsql_expr_walker_list(func, stmt_fori->body, expr_walker, context);
! }
!
case PLPGSQL_STMT_FORS:
! {
! PLpgSQL_stmt_fors *stmt_fors = (PLpgSQL_stmt_fors *) stmt;
!
! if (expr_walker(stmt, stmt_fors->query, context))
! return true;
!
! return plpgsql_expr_walker_list(func, stmt_fors->body, expr_walker, context);
! }
!
case PLPGSQL_STMT_FORC:
! {
! PLpgSQL_stmt_forc *stmt_forc = (PLpgSQL_stmt_forc *) stmt;
! PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_forc->curvar];
!
! if (expr_walker(stmt, stmt_forc->argquery, context))
! return true;
!
! if (expr_walker(stmt, var->cursor_explicit_expr, context))
! return true;
!
! return plpgsql_expr_walker_list(func, stmt_forc->body, expr_walker, context);
! }
!
case PLPGSQL_STMT_DYNFORS:
! {
! PLpgSQL_stmt_dynfors * stmt_dynfors = (PLpgSQL_stmt_dynfors *) stmt;
! if (expr_walker(stmt, stmt_dynfors->query, context))
! return true;
! foreach(l, stmt_dynfors->params)
! {
! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! return true;
! }
! return plpgsql_expr_walker_list(func, stmt_dynfors->body, expr_walker, context);
! }
! case PLPGSQL_STMT_FOREACH_A:
! {
! PLpgSQL_stmt_foreach_a *stmt_foreach_a = (PLpgSQL_stmt_foreach_a *) stmt;
! if (expr_walker(stmt, stmt_foreach_a->expr, context))
! return true;
! return plpgsql_expr_walker_list(func, stmt_foreach_a->body, expr_walker, context);
! }
! case PLPGSQL_STMT_EXIT:
! return expr_walker(stmt, ((PLpgSQL_stmt_exit *) stmt)->cond, context);
! case PLPGSQL_STMT_PERFORM:
! return expr_walker(stmt, ((PLpgSQL_stmt_perform *) stmt)->expr, context);
! case PLPGSQL_STMT_RETURN:
! return expr_walker(stmt, ((PLpgSQL_stmt_return *) stmt)->expr, context);
! case PLPGSQL_STMT_RETURN_NEXT:
! return expr_walker(stmt, ((PLpgSQL_stmt_return_next *) stmt)->expr, context);
! case PLPGSQL_STMT_RETURN_QUERY:
! {
! PLpgSQL_stmt_return_query *stmt_rq = (PLpgSQL_stmt_return_query *) stmt;
! if (expr_walker(stmt, stmt_rq->query, context))
! return true;
! if (expr_walker(stmt, stmt_rq->dynquery, context))
! return true;
! foreach(l, stmt_rq->params)
! {
! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! return true;
! }
! return false;
! }
! case PLPGSQL_STMT_RAISE:
! {
! PLpgSQL_stmt_raise *stmt_raise = (PLpgSQL_stmt_raise *) stmt;
! foreach(l, stmt_raise->params)
! {
! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! return true;
! }
! foreach(l, stmt_raise->options)
! {
! if (expr_walker(stmt, ((PLpgSQL_raise_option *) lfirst(l))->expr, context))
! return true;
! }
! return false;
! }
! case PLPGSQL_STMT_EXECSQL:
! return expr_walker(stmt, ((PLpgSQL_stmt_execsql *) stmt)->sqlstmt, context);
! case PLPGSQL_STMT_DYNEXECUTE:
! {
! PLpgSQL_stmt_dynexecute *stmt_dynexecute = (PLpgSQL_stmt_dynexecute *) stmt;
! if (expr_walker(stmt, stmt_dynexecute->query, context))
! return true;
! foreach(l, stmt_dynexecute->params)
! {
! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! return true;
! }
! return false;
! }
! break;
! case PLPGSQL_STMT_GETDIAG:
! return false;
! case PLPGSQL_STMT_OPEN:
! {
! PLpgSQL_stmt_open *stmt_open = (PLpgSQL_stmt_open *) stmt;
! PLpgSQL_var *var = (PLpgSQL_var *) func->datums[stmt_open->curvar];
! if (expr_walker(stmt, var->cursor_explicit_expr, context))
! return true;
! if (expr_walker(stmt, stmt_open->query, context))
! return true;
! if (expr_walker(stmt, stmt_open->dynquery, context))
! return true;
! if (expr_walker(stmt, stmt_open->argquery, context))
! return true;
! foreach(l, stmt_open->params)
! {
! if (expr_walker(stmt, (PLpgSQL_expr *) lfirst(l), context))
! return true;
! }
! return false;
! }
! case PLPGSQL_STMT_FETCH:
! case PLPGSQL_STMT_CLOSE:
! return false;
! default:
! elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
! return false; /* be compiler quite */
}
}
! /**********************************************************************
! * Release memory when a PL/pgSQL function is no longer needed
! *
! * The code for recursing through the function tree is really only
! * needed to locate PLpgSQL_expr nodes, which may contain references
! * to saved SPI Plans that must be freed. The function tree itself,
! * along with subsidiary data, is freed in one swoop by freeing the
! * function's permanent memory context.
! **********************************************************************/
! static bool
! free_expression(PLpgSQL_stmt *stmt, PLpgSQL_expr *expr, void *context)
{
if (expr && expr->plan)
{
SPI_freeplan(expr->plan);
expr->plan = NULL;
}
+ return false;
}
void
plpgsql_free_function_memory(PLpgSQL_function *func)
{
/* Better not call this on an in-use function */
Assert(func->use_count == 0);
! /* Release plans in statement tree */
! plpgsql_expr_walker(func, (PLpgSQL_stmt *) func->action,
! free_expression,
! NULL);
func->ndatums = 0;
func->action = NULL;
/*
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 34,40 **** static const struct config_enum_entry variable_conflict_options[] = {
--- 34,47 ----
{NULL, 0, false}
};
+ static const struct config_enum_entry prepare_plans_options[] = {
+ {"on_demand", PLPGSQL_PREPARE_PLANS_ON_DEMAND, false},
+ {"on_start", PLPGSQL_PREPARE_PLANS_ON_START, false},
+ {NULL, 0, false}
+ };
+
int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
+ int plpgsql_prepare_plans = PLPGSQL_PREPARE_PLANS_ON_DEMAND;
/* Hook for plugins */
PLpgSQL_plugin **plugin_ptr = NULL;
***************
*** 65,70 **** _PG_init(void)
--- 72,87 ----
PGC_SUSET, 0,
NULL, NULL, NULL);
+ DefineCustomEnumVariable("plpgsql.prepare_plans",
+ gettext_noop("Sets timing of preparation plans of embeded SQL."),
+ NULL,
+ &plpgsql_prepare_plans,
+ PLPGSQL_PREPARE_PLANS_ON_DEMAND,
+ prepare_plans_options,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+
+
EmitWarningsOnPlaceholders("plpgsql");
plpgsql_HashTableInit();
*** a/src/pl/plpgsql/src/pl_scanner.c
--- b/src/pl/plpgsql/src/pl_scanner.c
***************
*** 129,138 **** static const ScanKeyword unreserved_keywords[] = {
--- 129,141 ----
PG_KEYWORD("next", K_NEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("no", K_NO, UNRESERVED_KEYWORD)
PG_KEYWORD("notice", K_NOTICE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("on_demand", K_ON_DEMAND, UNRESERVED_KEYWORD)
+ PG_KEYWORD("on_start", K_ON_START, UNRESERVED_KEYWORD)
PG_KEYWORD("option", K_OPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL, UNRESERVED_KEYWORD)
PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT, UNRESERVED_KEYWORD)
+ PG_KEYWORD("prepare_plans", K_PREPARE_PLANS, UNRESERVED_KEYWORD)
PG_KEYWORD("prior", K_PRIOR, UNRESERVED_KEYWORD)
PG_KEYWORD("query", K_QUERY, UNRESERVED_KEYWORD)
PG_KEYWORD("relative", K_RELATIVE, UNRESERVED_KEYWORD)
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 157,162 **** typedef enum
--- 157,173 ----
PLPGSQL_RESOLVE_COLUMN /* prefer table column to plpgsql var */
} PLpgSQL_resolve_option;
+ /* --------
+ * Behavioral modes for plpgsql execution plan timing
+ * --------
+ */
+ typedef enum
+ {
+ PLPGSQL_PREPARE_PLANS_UNDEF,
+ PLPGSQL_PREPARE_PLANS_ON_DEMAND, /* prepare plan when it is necessary (default) */
+ PLPGSQL_PREPARE_PLANS_ON_START /* prepare all plans on start */
+ } PLpgSQL_prepare_plans_option;
+
/**********************************************************************
* Node and structure definitions
***************
*** 700,705 **** typedef struct PLpgSQL_function
--- 711,717 ----
int tg_argv_varno;
PLpgSQL_resolve_option resolve_option;
+ PLpgSQL_prepare_plans_option prepare_plans_option;
int ndatums;
PLpgSQL_datum **datums;
***************
*** 836,841 **** typedef enum
--- 848,854 ----
extern IdentifierLookup plpgsql_IdentifierLookup;
extern int plpgsql_variable_conflict;
+ extern int plpgsql_prepare_plans;
extern bool plpgsql_check_syntax;
extern bool plpgsql_DumpExecTree;
***************
*** 930,935 **** extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
--- 943,953 ----
extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,
const char *name);
+ extern bool plpgsql_expr_walker(PLpgSQL_function *func,
+ PLpgSQL_stmt *stmt,
+ bool (*expr_walker)(),
+ void *context);
+
/* ----------
* Other functions in pl_funcs.c
* ----------
*** a/src/test/regress/expected/plpgsql.out
--- b/src/test/regress/expected/plpgsql.out
***************
*** 1,3 ****
--- 1,5 ----
+ load 'plpgsql';
+ set plpgsql.prepare_plans to on_start;
--
-- PLPGSQL
--
***************
*** 2410,2415 **** drop function missing_return_expr();
--- 2412,2418 ----
create table eifoo (i integer, y integer);
create type eitype as (i integer, y integer);
create or replace function execute_into_test(varchar) returns record as $$
+ #prepare_plans on_demand
declare
_r record;
_rt eifoo%rowtype;
***************
*** 2469,2475 **** LINE 1: SELECT sqlstate
--- 2472,2486 ----
^
QUERY: SELECT sqlstate
CONTEXT: PL/pgSQL function "excpt_test2" line 5 at RAISE
+ /*
+ * this example has blocked a plan preparation on start
+ * because we would to demonstrate a nested trapping errors.
+ * When we use a plans generated on start, then we raise
+ * exception before we start a protected section, because
+ * exception is raised when plan is generated.
+ */
create function excpt_test3() returns void as $$
+ #prepare_plans on_demand
begin
begin
raise exception 'user exception';
***************
*** 2527,2532 **** insert into conttesttbl(v) values(20);
--- 2538,2544 ----
insert into conttesttbl(v) values(30);
insert into conttesttbl(v) values(40);
create function continue_test1() returns void as $$
+ #prepare_plans on_demand
declare _i integer = 0; _r record;
begin
raise notice '---1---';
***************
*** 2852,2857 **** select footest();
--- 2864,2870 ----
ERROR: query returned more than one row
CONTEXT: PL/pgSQL function "footest" line 5 at SQL statement
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should work
***************
*** 2866,2871 **** NOTICE: x.f1 = 5, x.f2 = 6
--- 2879,2885 ----
(1 row)
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- this should work since EXECUTE isn't as picky
***************
*** 2925,2930 **** select footest();
--- 2939,2945 ----
ERROR: query returned more than one row
CONTEXT: PL/pgSQL function "footest" line 5 at SQL statement
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should work
***************
*** 2939,2944 **** NOTICE: x.f1 = 3, x.f2 = 4
--- 2954,2960 ----
(1 row)
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should fail, no rows
***************
*** 2947,2954 **** begin
end$$ language plpgsql;
select footest();
ERROR: query returned no rows
! CONTEXT: PL/pgSQL function "footest" line 5 at EXECUTE statement
create or replace function footest() returns void as $$
declare x record;
begin
-- should fail, too many rows
--- 2963,2971 ----
end$$ language plpgsql;
select footest();
ERROR: query returned no rows
! CONTEXT: PL/pgSQL function "footest" line 6 at EXECUTE statement
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should fail, too many rows
***************
*** 2957,2963 **** begin
end$$ language plpgsql;
select footest();
ERROR: query returned more than one row
! CONTEXT: PL/pgSQL function "footest" line 5 at EXECUTE statement
drop function footest();
-- test scrollable cursor support
create function sc_test() returns setof integer as $$
--- 2974,2980 ----
end$$ language plpgsql;
select footest();
ERROR: query returned more than one row
! CONTEXT: PL/pgSQL function "footest" line 6 at EXECUTE statement
drop function footest();
-- test scrollable cursor support
create function sc_test() returns setof integer as $$
***************
*** 3027,3032 **** select * from sc_test();
--- 3044,3050 ----
(5 rows)
create or replace function sc_test() returns setof integer as $$
+ #prepare_plans on_demand
declare
c refcursor;
x integer;
***************
*** 3049,3054 **** select * from sc_test();
--- 3067,3073 ----
(3 rows)
create or replace function sc_test() returns setof integer as $$
+ #prepare_plans on_demand
declare
c refcursor;
x integer;
***************
*** 3220,3225 **** NOTICE: 6
--- 3239,3245 ----
drop function exc_using(int, text);
create or replace function exc_using(int) returns void as $$
+ #prepare_plans on_demand
declare
c refcursor;
i int;
***************
*** 3352,3357 **** select * from forc_test;
--- 3372,3378 ----
-- same, with a cursor whose portal name doesn't match variable name
create or replace function forc01() returns void as $$
+ #prepare_plans on_demand
declare
c refcursor := 'fooled_ya';
r record;
***************
*** 3685,3690 **** drop function stacked_diagnostics_test();
--- 3706,3712 ----
-- check cases where implicit SQLSTATE variable could be confused with
-- SQLSTATE as a keyword, cf bug #5524
create or replace function raise_test() returns void as $$
+ #prepare_plans on_demand
begin
perform 1/0;
exception
***************
*** 3772,3778 **** select case_test(13); -- fails
--- 3794,3807 ----
ERROR: case not found
HINT: CASE statement is missing ELSE part.
CONTEXT: PL/pgSQL function "case_test" line 5 at CASE
+ /*
+ * should to have blocked early plan preparation, because
+ * a case_test(6) is evaluated too early - it should be
+ * replaced by constant, and then it cannot to demonstrate
+ * a expected efect.
+ */
create or replace function catch() returns void as $$
+ #prepare_plans on_demand
begin
raise notice '%', case_test(6);
exception
***************
*** 4131,4143 **** LINE 4: return 'foo\\bar\041baz';
^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest();
- NOTICE: foo\bar!baz
WARNING: nonstandard use of \\ in a string literal
LINE 1: SELECT 'foo\\bar\041baz'
^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
QUERY: SELECT 'foo\\bar\041baz'
CONTEXT: PL/pgSQL function "strtest" line 4 at RETURN
strtest
-------------
foo\bar!baz
--- 4160,4172 ----
^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
select strtest();
WARNING: nonstandard use of \\ in a string literal
LINE 1: SELECT 'foo\\bar\041baz'
^
HINT: Use the escape string syntax for backslashes, e.g., E'\\'.
QUERY: SELECT 'foo\\bar\041baz'
CONTEXT: PL/pgSQL function "strtest" line 4 at RETURN
+ NOTICE: foo\bar!baz
strtest
-------------
foo\bar!baz
*** a/src/test/regress/sql/plpgsql.sql
--- b/src/test/regress/sql/plpgsql.sql
***************
*** 1,3 ****
--- 1,6 ----
+ load 'plpgsql';
+ set plpgsql.prepare_plans to on_start;
+
--
-- PLPGSQL
--
***************
*** 2052,2057 **** create table eifoo (i integer, y integer);
--- 2055,2061 ----
create type eitype as (i integer, y integer);
create or replace function execute_into_test(varchar) returns record as $$
+ #prepare_plans on_demand
declare
_r record;
_rt eifoo%rowtype;
***************
*** 2099,2105 **** end; $$ language plpgsql;
--- 2103,2117 ----
-- should fail
select excpt_test2();
+ /*
+ * this example has blocked a plan preparation on start
+ * because we would to demonstrate a nested trapping errors.
+ * When we use a plans generated on start, then we raise
+ * exception before we start a protected section, because
+ * exception is raised when plan is generated.
+ */
create function excpt_test3() returns void as $$
+ #prepare_plans on_demand
begin
begin
raise exception 'user exception';
***************
*** 2146,2151 **** insert into conttesttbl(v) values(30);
--- 2158,2164 ----
insert into conttesttbl(v) values(40);
create function continue_test1() returns void as $$
+ #prepare_plans on_demand
declare _i integer = 0; _r record;
begin
raise notice '---1---';
***************
*** 2381,2386 **** end$$ language plpgsql;
--- 2394,2400 ----
select footest();
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should work
***************
*** 2391,2396 **** end$$ language plpgsql;
--- 2405,2411 ----
select footest();
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- this should work since EXECUTE isn't as picky
***************
*** 2433,2438 **** end$$ language plpgsql;
--- 2448,2454 ----
select footest();
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should work
***************
*** 2443,2448 **** end$$ language plpgsql;
--- 2459,2465 ----
select footest();
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should fail, no rows
***************
*** 2453,2458 **** end$$ language plpgsql;
--- 2470,2476 ----
select footest();
create or replace function footest() returns void as $$
+ #prepare_plans on_demand
declare x record;
begin
-- should fail, too many rows
***************
*** 2518,2523 **** $$ language plpgsql;
--- 2536,2542 ----
select * from sc_test();
create or replace function sc_test() returns setof integer as $$
+ #prepare_plans on_demand
declare
c refcursor;
x integer;
***************
*** 2535,2540 **** $$ language plpgsql;
--- 2554,2560 ----
select * from sc_test();
create or replace function sc_test() returns setof integer as $$
+ #prepare_plans on_demand
declare
c refcursor;
x integer;
***************
*** 2657,2662 **** select exc_using(5, 'foobar');
--- 2677,2683 ----
drop function exc_using(int, text);
create or replace function exc_using(int) returns void as $$
+ #prepare_plans on_demand
declare
c refcursor;
i int;
***************
*** 2738,2743 **** select * from forc_test;
--- 2759,2765 ----
-- same, with a cursor whose portal name doesn't match variable name
create or replace function forc01() returns void as $$
+ #prepare_plans on_demand
declare
c refcursor := 'fooled_ya';
r record;
***************
*** 3014,3019 **** drop function stacked_diagnostics_test();
--- 3036,3042 ----
-- check cases where implicit SQLSTATE variable could be confused with
-- SQLSTATE as a keyword, cf bug #5524
create or replace function raise_test() returns void as $$
+ #prepare_plans on_demand
begin
perform 1/0;
exception
***************
*** 3059,3065 **** select case_test(11);
--- 3082,3095 ----
select case_test(12);
select case_test(13); -- fails
+ /*
+ * should to have blocked early plan preparation, because
+ * a case_test(6) is evaluated too early - it should be
+ * replaced by constant, and then it cannot to demonstrate
+ * a expected efect.
+ */
create or replace function catch() returns void as $$
+ #prepare_plans on_demand
begin
raise notice '%', case_test(6);
exception