Andres Freund <and...@anarazel.de> writes: > I'm a bit worried about a case like:
> CREATE FUNCTION yell(int, int) > RETURNS int > IMMUTABLE > LANGUAGE SQL AS $$ > SELECT CASE WHEN $1 != 0 THEN 17 / $2 ELSE NULL END > $$; > EXPLAIN SELECT yell(g.i, 0) FROM generate_series(1, 10) g(i); > I don't think the parameters here would have been handled before > inlining, right? Ah, I see what you mean. Yeah, that throws an error today, and it still would with the patch I was envisioning (attached), because inlining does Param substitution in a different way. I'm not sure that we could realistically fix the inlining case with this sort of approach. I think this bears out the comment I made before that this approach still leaves us with a very complicated behavior. Maybe we should stick with the previous approach, possibly supplemented with a leakproofness exception. regards, tom lane
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index e04b144072..4ff7a69908 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -61,6 +61,13 @@ typedef struct AggClauseCosts *costs; } get_agg_clause_costs_context; +typedef enum +{ + ece_param_never, /* never inject values for Params */ + ece_param_const, /* inject values for Params if marked CONST */ + ece_param_always /* always inject values for Params */ +} ece_p_mode; + typedef struct { ParamListInfo boundParams; @@ -68,6 +75,7 @@ typedef struct List *active_fns; Node *case_val; bool estimate; + ece_p_mode param_mode; } eval_const_expressions_context; typedef struct @@ -2264,6 +2272,7 @@ eval_const_expressions(PlannerInfo *root, Node *node) context.active_fns = NIL; /* nothing being recursively simplified */ context.case_val = NULL; /* no CASE being examined */ context.estimate = false; /* safe transformations only */ + context.param_mode = ece_param_const; /* inject only CONST Params */ return eval_const_expressions_mutator(node, &context); } @@ -2280,8 +2289,11 @@ eval_const_expressions(PlannerInfo *root, Node *node) * available by the caller of planner(), even if the Param isn't marked * constant. This effectively means that we plan using the first supplied * value of the Param. - * 2. Fold stable, as well as immutable, functions to constants. + * 2. Fold stable, as well as immutable, functions to constants. The risk + * that the result might change from planning time to execution time is + * worth taking, as we otherwise couldn't get an estimate at all. * 3. Reduce PlaceHolderVar nodes to their contained expressions. + * 4. Ignore domain constraints, assuming that CoerceToDomain will succeed. *-------------------- */ Node * @@ -2295,6 +2307,7 @@ estimate_expression_value(PlannerInfo *root, Node *node) context.active_fns = NIL; /* nothing being recursively simplified */ context.case_val = NULL; /* no CASE being examined */ context.estimate = true; /* unsafe transformations OK */ + context.param_mode = ece_param_always; /* inject all Param values */ return eval_const_expressions_mutator(node, &context); } @@ -2372,8 +2385,22 @@ eval_const_expressions_mutator(Node *node, prm->ptype == param->paramtype) { /* OK to substitute parameter value? */ - if (context->estimate || - (prm->pflags & PARAM_FLAG_CONST)) + bool subst = false; + + switch (context->param_mode) + { + case ece_param_never: + /* subst is already false */ + break; + case ece_param_const: + subst = (prm->pflags & PARAM_FLAG_CONST) != 0; + break; + case ece_param_always: + subst = true; + break; + } + + if (subst) { /* * Return a Const representing the param value. @@ -2973,10 +3000,26 @@ eval_const_expressions_mutator(Node *node, * expression when executing the CASE, since any contained * CaseTestExprs that might have referred to it will have been * replaced by the constant. + * + * An additional consideration is that the user might be + * expecting the CASE to prevent run-time errors, as in + * CASE ... THEN 1/$1 ELSE ... + * If a constant value of zero is available for $1, we would + * end up trying to simplify the division, thus drawing a + * divide-by-zero error even if this CASE result would not be + * reached at runtime. This is problematic because it can + * occur even if the user has not written anything as silly as + * a constant "1/0" expression: substitution of values for + * Params is standard in plpgsql, for instance. To ameliorate + * this problem without giving up const-simplification of CASE + * subexpressions entirely, we prevent substitution of Param + * values within test condition or result expressions that + * will not certainly be evaluated at run-time. *---------- */ CaseExpr *caseexpr = (CaseExpr *) node; CaseExpr *newcase; + ece_p_mode save_param_mode = context->param_mode; Node *save_case_val; Node *newarg; List *newargs; @@ -3027,6 +3070,15 @@ eval_const_expressions_mutator(Node *node, const_true_cond = true; } + /* + * Unless the test condition is constant TRUE, we can't be + * sure the result value will be evaluated, so back off + * the Param safety level. This change will also apply to + * subsequent test conditions and result values. + */ + if (!const_true_cond) + context->param_mode = ece_param_never; + /* Simplify this alternative's result value */ caseresult = eval_const_expressions_mutator((Node *) oldcasewhen->result, context); @@ -3058,6 +3110,7 @@ eval_const_expressions_mutator(Node *node, context); context->case_val = save_case_val; + context->param_mode = save_param_mode; /* * If no non-FALSE alternatives, CASE reduces to the default @@ -3113,6 +3166,7 @@ eval_const_expressions_mutator(Node *node, { CoalesceExpr *coalesceexpr = (CoalesceExpr *) node; CoalesceExpr *newcoalesce; + ece_p_mode save_param_mode = context->param_mode; List *newargs; ListCell *arg; @@ -3137,13 +3191,25 @@ eval_const_expressions_mutator(Node *node, if (((Const *) e)->constisnull) continue; /* drop null constant */ if (newargs == NIL) + { + context->param_mode = save_param_mode; return e; /* first expr */ + } newargs = lappend(newargs, e); break; } newargs = lappend(newargs, e); + + /* + * Arguments following a non-constant argument may or may + * not get evaluated at run-time. As in CASE expressions, + * avoid substituting Param values within such arguments. + */ + context->param_mode = ece_param_never; } + context->param_mode = save_param_mode; + /* * If all the arguments were constant null, the result is just * null