* Make a plan if we don't have one already.
*/
if (expr->plan == NULL)
- {
exec_prepare_plan(estate, expr, 0);
- /*
- * A CALL or DO can never be a simple expression.
- */
- Assert(!expr->expr_simple_expr);
+ /*
+ * A CALL or DO can never be a simple expression.
+ */
+ Assert(!expr->expr_simple_expr);
- /*
- * Also construct a DTYPE_ROW datum representing the plpgsql variables
- * associated with the procedure's output arguments. Then we can use
- * exec_move_row() to do the assignments.
- */
- if (stmt->is_call)
- stmt->target = make_callstmt_target(estate, expr);
- }
+ /*
+ * Also construct a DTYPE_ROW datum representing the plpgsql variables
+ * associated with the procedure's output arguments. Then we can use
+ * exec_move_row() to do the assignments.
+ */
+ if (stmt->is_call && stmt->target == NULL)
+ stmt->target = make_callstmt_target(estate, expr);
paramLI = setup_param_list(estate, expr);
/* ----------
* Generate a prepared plan
+ *
+ * CAUTION: it is possible for this function to throw an error after it has
+ * built a SPIPlan and saved it in expr->plan. Therefore, be wary of doing
+ * additional things contingent on expr->plan being NULL. That is, given
+ * code like
+ *
+ * if (query->plan == NULL)
+ * {
+ * // okay to put setup code here
+ * exec_prepare_plan(estate, query, ...);
+ * // NOT okay to put more logic here
+ * }
+ *
+ * extra steps at the end are unsafe because they will not be executed when
+ * re-executing the calling statement, if exec_prepare_plan failed the first
+ * time. This is annoyingly error-prone, but the alternatives are worse.
* ----------
*/
static void
* whether the statement is INSERT/UPDATE/DELETE
*/
if (expr->plan == NULL)
+ exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+
+ if (!stmt->mod_stmt_set)
{
ListCell *l;
- exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
stmt->mod_stmt = false;
foreach(l, SPI_plan_get_plan_sources(expr->plan))
{
break;
}
}
+ stmt->mod_stmt_set = true;
}
/*
* exec_simple_check_plan - Check if a plan is simple enough to
* be evaluated by ExecEvalExpr() instead
* of SPI.
+ *
+ * Note: the refcount manipulations in this function assume that expr->plan
+ * is a "saved" SPI plan. That's a bit annoying from the caller's standpoint,
+ * but it's otherwise difficult to avoid leaking the plan on failure.
* ----------
*/
static void
* OK, we can treat it as a simple plan.
*
* Get the generic plan for the query. If replanning is needed, do that
- * work in the eval_mcontext.
+ * work in the eval_mcontext. (Note that replanning could throw an error,
+ * in which case the expr is left marked "not simple", which is fine.)
*/
oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
cplan = SPI_plan_get_cached_plan(expr->plan);