@@ -333,8 +333,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
333
333
bool keepplan );
334
334
static void exec_simple_check_plan (PLpgSQL_execstate * estate , PLpgSQL_expr * expr );
335
335
static 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 );
338
337
static bool exec_eval_simple_expr (PLpgSQL_execstate * estate ,
339
338
PLpgSQL_expr * expr ,
340
339
Datum * result ,
@@ -4190,13 +4189,6 @@ exec_prepare_plan(PLpgSQL_execstate *estate,
4190
4189
4191
4190
/* Check to see if it's a simple expression */
4192
4191
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 ;
4200
4192
}
4201
4193
4202
4194
@@ -5024,16 +5016,23 @@ exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
5024
5016
int32 valtypmod ;
5025
5017
5026
5018
/*
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.
5031
5020
*/
5032
5021
if (expr -> plan == NULL )
5033
5022
{
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
+ */
5035
5030
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);
5037
5036
}
5038
5037
5039
5038
value = exec_eval_expr (estate , expr , & isnull , & valtype , & valtypmod );
@@ -6098,6 +6097,7 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
6098
6097
ReleaseCachedPlan (cplan , true);
6099
6098
/* Mark expression as non-simple, and fail */
6100
6099
expr -> expr_simple_expr = NULL ;
6100
+ expr -> expr_rw_param = NULL ;
6101
6101
return false;
6102
6102
}
6103
6103
@@ -6109,10 +6109,6 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
6109
6109
6110
6110
/* Extract desired scalar expression from cached plan */
6111
6111
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 );
6116
6112
}
6117
6113
6118
6114
/*
@@ -6385,20 +6381,18 @@ plpgsql_param_fetch(ParamListInfo params,
6385
6381
prm -> pflags = PARAM_FLAG_CONST ;
6386
6382
6387
6383
/*
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.)
6390
6387
*/
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 );
6402
6396
6403
6397
return prm ;
6404
6398
}
@@ -6441,7 +6435,7 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
6441
6435
*/
6442
6436
if (datum -> dtype == PLPGSQL_DTYPE_VAR )
6443
6437
{
6444
- if (dno != expr -> rwparam &&
6438
+ if (param != expr -> expr_rw_param &&
6445
6439
((PLpgSQL_var * ) datum )-> datatype -> typlen == -1 )
6446
6440
scratch .d .cparam .paramfunc = plpgsql_param_eval_var_ro ;
6447
6441
else
@@ -6451,14 +6445,14 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
6451
6445
scratch .d .cparam .paramfunc = plpgsql_param_eval_recfield ;
6452
6446
else if (datum -> dtype == PLPGSQL_DTYPE_PROMISE )
6453
6447
{
6454
- if (dno != expr -> rwparam &&
6448
+ if (param != expr -> expr_rw_param &&
6455
6449
((PLpgSQL_var * ) datum )-> datatype -> typlen == -1 )
6456
6450
scratch .d .cparam .paramfunc = plpgsql_param_eval_generic_ro ;
6457
6451
else
6458
6452
scratch .d .cparam .paramfunc = plpgsql_param_eval_generic ;
6459
6453
}
6460
6454
else if (datum -> dtype == PLPGSQL_DTYPE_REC &&
6461
- dno != expr -> rwparam )
6455
+ param != expr -> expr_rw_param )
6462
6456
scratch .d .cparam .paramfunc = plpgsql_param_eval_generic_ro ;
6463
6457
else
6464
6458
scratch .d .cparam .paramfunc = plpgsql_param_eval_generic ;
@@ -7930,6 +7924,7 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
7930
7924
* Initialize to "not simple".
7931
7925
*/
7932
7926
expr -> expr_simple_expr = NULL ;
7927
+ expr -> expr_rw_param = NULL ;
7933
7928
7934
7929
/*
7935
7930
* 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)
8108
8103
expr -> expr_simple_typmod = exprTypmod ((Node * ) tle_expr );
8109
8104
/* We also want to remember if it is immutable or not */
8110
8105
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 );
8111
8112
}
8112
8113
8113
8114
/*
@@ -8119,25 +8120,36 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
8119
8120
* value as a read/write pointer and let the function modify the value
8120
8121
* in-place.
8121
8122
*
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.
8124
8138
*/
8125
8139
static void
8126
- exec_check_rw_parameter (PLpgSQL_expr * expr , int target_dno )
8140
+ exec_check_rw_parameter (PLpgSQL_expr * expr )
8127
8141
{
8142
+ int target_dno ;
8128
8143
Oid funcid ;
8129
8144
List * fargs ;
8130
8145
ListCell * lc ;
8131
8146
8132
8147
/* Assume unsafe */
8133
- expr -> rwparam = -1 ;
8148
+ expr -> expr_rw_param = NULL ;
8134
8149
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 )
8141
8153
return ;
8142
8154
8143
8155
/*
@@ -8147,9 +8159,12 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
8147
8159
if (!bms_is_member (target_dno , expr -> paramnos ))
8148
8160
return ;
8149
8161
8162
+ /* Shouldn't be here for non-simple expression */
8163
+ Assert (expr -> expr_simple_expr != NULL );
8164
+
8150
8165
/*
8151
8166
* Top level of expression must be a simple FuncExpr, OpExpr, or
8152
- * SubscriptingRef.
8167
+ * SubscriptingRef, else we can't optimize .
8153
8168
*/
8154
8169
if (IsA (expr -> expr_simple_expr , FuncExpr ))
8155
8170
{
@@ -8174,22 +8189,20 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
8174
8189
F_ARRAY_SUBSCRIPT_HANDLER )
8175
8190
return ;
8176
8191
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 ;
8181
8196
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
+ }
8190
8205
8191
- /* OK, we can pass target as a read-write parameter */
8192
- expr -> rwparam = target_dno ;
8193
8206
return ;
8194
8207
}
8195
8208
else
@@ -8205,44 +8218,28 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int target_dno)
8205
8218
return ;
8206
8219
8207
8220
/*
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.
8210
8225
*/
8211
8226
foreach (lc , fargs )
8212
8227
{
8213
8228
Node * arg = (Node * ) lfirst (lc );
8214
8229
8215
- /* A Param is OK, whether it's the target variable or not */
8216
8230
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 ;
8238
8233
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
+ }
8243
8242
}
8244
- return expression_tree_walker (node , contains_target_param ,
8245
- (void * ) target_dno );
8246
8243
}
8247
8244
8248
8245
/* ----------
0 commit comments