@@ -333,8 +333,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
333333 bool keepplan );
334334static void exec_simple_check_plan (PLpgSQL_execstate * estate , PLpgSQL_expr * expr );
335335static void exec_save_simple_expr (PLpgSQL_expr * expr , CachedPlan * cplan );
336- static void exec_check_rw_parameter (PLpgSQL_expr * expr , int target_dno );
337- static bool contains_target_param (Node * node , int * target_dno );
336+ static void exec_check_rw_parameter (PLpgSQL_expr * expr );
338337static bool exec_eval_simple_expr (PLpgSQL_execstate * estate ,
339338 PLpgSQL_expr * expr ,
340339 Datum * result ,
@@ -4190,13 +4189,6 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
41904189
41914190 /* Check to see if it's a simple expression */
41924191 exec_simple_check_plan (estate , expr );
4193-
4194- /*
4195- * Mark expression as not using a read-write param. exec_assign_value has
4196- * to take steps to override this if appropriate; that seems cleaner than
4197- * adding parameters to all other callers.
4198- */
4199- expr -> rwparam = -1 ;
42004192}
42014193
42024194
@@ -5024,16 +5016,23 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
50245016 int32 valtypmod ;
50255017
50265018 /*
5027- * If first time through, create a plan for this expression, and then see
5028- * if we can pass the target variable as a read-write parameter to the
5029- * expression. (This is a bit messy, but it seems cleaner than modifying
5030- * the API of exec_eval_expr for the purpose.)
5019+ * If first time through, create a plan for this expression.
50315020 */
50325021 if (expr -> plan == NULL )
50335022 {
5034- exec_prepare_plan (estate , expr , 0 , true);
5023+ /*
5024+ * Mark the expression as being an assignment source, if target is a
5025+ * simple variable. (This is a bit messy, but it seems cleaner than
5026+ * modifying the API of exec_prepare_plan for the purpose. We need to
5027+ * stash the target dno into the expr anyway, so that it will be
5028+ * available if we have to replan.)
5029+ */
50355030 if (target -> dtype == PLPGSQL_DTYPE_VAR )
5036- exec_check_rw_parameter (expr , target -> dno );
5031+ expr -> target_param = target -> dno ;
5032+ else
5033+ expr -> target_param = -1 ; /* should be that already */
5034+
5035+ exec_prepare_plan (estate , expr , 0 , true);
50375036 }
50385037
50395038 value = exec_eval_expr (estate , expr , & isnull , & valtype , & valtypmod );
@@ -6098,6 +6097,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
60986097 ReleaseCachedPlan (cplan , true);
60996098 /* Mark expression as non-simple, and fail */
61006099 expr -> expr_simple_expr = NULL ;
6100+ expr -> expr_rw_param = NULL ;
61016101 return false;
61026102 }
61036103
@@ -6109,10 +6109,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
61096109
61106110 /* Extract desired scalar expression from cached plan */
61116111 exec_save_simple_expr (expr , cplan );
6112-
6113- /* better recheck r/w safety, as it could change due to inlining */
6114- if (expr -> rwparam >= 0 )
6115- exec_check_rw_parameter (expr , expr -> rwparam );
61166112 }
61176113
61186114 /*
@@ -6385,20 +6381,18 @@ plpgsql_param_fetch(ParamListInfo params,
63856381 prm -> pflags = PARAM_FLAG_CONST ;
63866382
63876383 /*
6388- * If it's a read/write expanded datum, convert reference to read-only,
6389- * unless it's safe to pass as read-write.
6384+ * If it's a read/write expanded datum, convert reference to read-only.
6385+ * (There's little point in trying to optimize read/write parameters,
6386+ * given the cases in which this function is used.)
63906387 */
6391- if (dno != expr -> rwparam )
6392- {
6393- if (datum -> dtype == PLPGSQL_DTYPE_VAR )
6394- prm -> value = MakeExpandedObjectReadOnly (prm -> value ,
6395- prm -> isnull ,
6396- ((PLpgSQL_var * ) datum )-> datatype -> typlen );
6397- else if (datum -> dtype == PLPGSQL_DTYPE_REC )
6398- prm -> value = MakeExpandedObjectReadOnly (prm -> value ,
6399- prm -> isnull ,
6400- -1 );
6401- }
6388+ if (datum -> dtype == PLPGSQL_DTYPE_VAR )
6389+ prm -> value = MakeExpandedObjectReadOnly (prm -> value ,
6390+ prm -> isnull ,
6391+ ((PLpgSQL_var * ) datum )-> datatype -> typlen );
6392+ else if (datum -> dtype == PLPGSQL_DTYPE_REC )
6393+ prm -> value = MakeExpandedObjectReadOnly (prm -> value ,
6394+ prm -> isnull ,
6395+ -1 );
64026396
64036397 return prm ;
64046398}
@@ -6441,7 +6435,7 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
64416435 */
64426436 if (datum -> dtype == PLPGSQL_DTYPE_VAR )
64436437 {
6444- if (dno != expr -> rwparam &&
6438+ if (param != expr -> expr_rw_param &&
64456439 ((PLpgSQL_var * ) datum )-> datatype -> typlen == -1 )
64466440 scratch .d .cparam .paramfunc = plpgsql_param_eval_var_ro ;
64476441 else
@@ -6451,14 +6445,14 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
64516445 scratch .d .cparam .paramfunc = plpgsql_param_eval_recfield ;
64526446 else if (datum -> dtype == PLPGSQL_DTYPE_PROMISE )
64536447 {
6454- if (dno != expr -> rwparam &&
6448+ if (param != expr -> expr_rw_param &&
64556449 ((PLpgSQL_var * ) datum )-> datatype -> typlen == -1 )
64566450 scratch .d .cparam .paramfunc = plpgsql_param_eval_generic_ro ;
64576451 else
64586452 scratch .d .cparam .paramfunc = plpgsql_param_eval_generic ;
64596453 }
64606454 else if (datum -> dtype == PLPGSQL_DTYPE_REC &&
6461- dno != expr -> rwparam )
6455+ param != expr -> expr_rw_param )
64626456 scratch .d .cparam .paramfunc = plpgsql_param_eval_generic_ro ;
64636457 else
64646458 scratch .d .cparam .paramfunc = plpgsql_param_eval_generic ;
@@ -7930,6 +7924,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
79307924 * Initialize to "not simple".
79317925 */
79327926 expr -> expr_simple_expr = NULL ;
7927+ expr -> expr_rw_param = NULL ;
79337928
79347929 /*
79357930 * Check the analyzed-and-rewritten form of the query to see if we will be
@@ -8108,6 +8103,12 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
81088103 expr -> expr_simple_typmod = exprTypmod ((Node * ) tle_expr );
81098104 /* We also want to remember if it is immutable or not */
81108105 expr -> expr_simple_mutable = contain_mutable_functions ((Node * ) tle_expr );
8106+
8107+ /*
8108+ * Lastly, check to see if there's a possibility of optimizing a
8109+ * read/write parameter.
8110+ */
8111+ exec_check_rw_parameter (expr );
81118112}
81128113
81138114/*
@@ -8119,25 +8120,36 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
81198120 * value as a read/write pointer and let the function modify the value
81208121 * in-place.
81218122 *
8122- * This function checks for a safe expression, and sets expr->rwparam to the
8123- * dno of the target variable (x) if safe, or -1 if not safe.
8123+ * This function checks for a safe expression, and sets expr->expr_rw_param
8124+ * to the address of any Param within the expression that can be passed as
8125+ * read/write (there can be only one); or to NULL when there is no safe Param.
8126+ *
8127+ * Note that this mechanism intentionally applies the safety labeling to just
8128+ * one Param; the expression could contain other Params referencing the target
8129+ * variable, but those must still be treated as read-only.
8130+ *
8131+ * Also note that we only apply this optimization within simple expressions.
8132+ * There's no point in it for non-simple expressions, because the
8133+ * exec_run_select code path will flatten any expanded result anyway.
8134+ * Also, it's safe to assume that an expr_simple_expr tree won't get copied
8135+ * somewhere before it gets compiled, so that looking for pointer equality
8136+ * to expr_rw_param will work for matching the target Param. That'd be much
8137+ * shakier in the general case.
81248138 */
81258139static void
8126- exec_check_rw_parameter (PLpgSQL_expr * expr , int target_dno )
8140+ exec_check_rw_parameter (PLpgSQL_expr * expr )
81278141{
8142+ int target_dno ;
81288143 Oid funcid ;
81298144 List * fargs ;
81308145 ListCell * lc ;
81318146
81328147 /* Assume unsafe */
8133- expr -> rwparam = -1 ;
8148+ expr -> expr_rw_param = NULL ;
81348149
8135- /*
8136- * If the expression isn't simple, there's no point in trying to optimize
8137- * (because the exec_run_select code path will flatten any expanded result
8138- * anyway). Even without that, this seems like a good safety restriction.
8139- */
8140- if (expr -> expr_simple_expr == NULL )
8150+ /* Done if expression isn't an assignment source */
8151+ target_dno = expr -> target_param ;
8152+ if (target_dno < 0 )
81418153 return ;
81428154
81438155 /*
@@ -8147,9 +8159,12 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
81478159 if (!bms_is_member (target_dno , expr -> paramnos ))
81488160 return ;
81498161
8162+ /* Shouldn't be here for non-simple expression */
8163+ Assert (expr -> expr_simple_expr != NULL );
8164+
81508165 /*
81518166 * Top level of expression must be a simple FuncExpr, OpExpr, or
8152- * SubscriptingRef.
8167+ * SubscriptingRef, else we can't optimize .
81538168 */
81548169 if (IsA (expr -> expr_simple_expr , FuncExpr ))
81558170 {
@@ -8174,22 +8189,20 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
81748189 F_ARRAY_SUBSCRIPT_HANDLER )
81758190 return ;
81768191
8177- /* refexpr can be a simple Param, otherwise must not contain target */
8178- if (!( sbsref -> refexpr && IsA (sbsref -> refexpr , Param )) &&
8179- contains_target_param (( Node * ) sbsref -> refexpr , & target_dno ))
8180- return ;
8192+ /* We can optimize the refexpr if it's the target, otherwise not */
8193+ if (sbsref -> refexpr && IsA (sbsref -> refexpr , Param ))
8194+ {
8195+ Param * param = ( Param * ) sbsref -> refexpr ;
81818196
8182- /* the other subexpressions must not contain target */
8183- if ( contains_target_param (( Node * ) sbsref -> refupperindexpr ,
8184- & target_dno ) ||
8185- contains_target_param (( Node * ) sbsref -> reflowerindexpr ,
8186- & target_dno ) ||
8187- contains_target_param (( Node * ) sbsref -> refassgnexpr ,
8188- & target_dno ))
8189- return ;
8197+ if ( param -> paramkind == PARAM_EXTERN &&
8198+ param -> paramid == target_dno + 1 )
8199+ {
8200+ /* Found the Param we want to pass as read/write */
8201+ expr -> expr_rw_param = param ;
8202+ return ;
8203+ }
8204+ }
81908205
8191- /* OK, we can pass target as a read-write parameter */
8192- expr -> rwparam = target_dno ;
81938206 return ;
81948207 }
81958208 else
@@ -8205,44 +8218,28 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
82058218 return ;
82068219
82078220 /*
8208- * The target variable (in the form of a Param) must only appear as a
8209- * direct argument of the top-level function.
8221+ * The target variable (in the form of a Param) must appear as a direct
8222+ * argument of the top-level function. References further down in the
8223+ * tree can't be optimized; but on the other hand, they don't invalidate
8224+ * optimizing the top-level call, since that will be executed last.
82108225 */
82118226 foreach (lc , fargs )
82128227 {
82138228 Node * arg = (Node * ) lfirst (lc );
82148229
8215- /* A Param is OK, whether it's the target variable or not */
82168230 if (arg && IsA (arg , Param ))
8217- continue ;
8218- /* Otherwise, argument expression must not reference target */
8219- if (contains_target_param (arg , & target_dno ))
8220- return ;
8221- }
8222-
8223- /* OK, we can pass target as a read-write parameter */
8224- expr -> rwparam = target_dno ;
8225- }
8226-
8227- /*
8228- * Recursively check for a Param referencing the target variable
8229- */
8230- static bool
8231- contains_target_param (Node * node , int * target_dno )
8232- {
8233- if (node == NULL )
8234- return false;
8235- if (IsA (node , Param ))
8236- {
8237- Param * param = (Param * ) node ;
8231+ {
8232+ Param * param = (Param * ) arg ;
82388233
8239- if (param -> paramkind == PARAM_EXTERN &&
8240- param -> paramid == * target_dno + 1 )
8241- return true;
8242- return false;
8234+ if (param -> paramkind == PARAM_EXTERN &&
8235+ param -> paramid == target_dno + 1 )
8236+ {
8237+ /* Found the Param we want to pass as read/write */
8238+ expr -> expr_rw_param = param ;
8239+ return ;
8240+ }
8241+ }
82438242 }
8244- return expression_tree_walker (node , contains_target_param ,
8245- (void * ) target_dno );
82468243}
82478244
82488245/* ----------
0 commit comments