Skip to content

Commit 1c1cbe2

Browse files
committed
Rethink the "read/write parameter" mechanism in pl/pgsql.
Performance issues with the preceding patch to re-implement array element assignment within pl/pgsql led me to realize that the read/write parameter mechanism is misdesigned. Instead of requiring the assignment source expression to be such that *all* its references to the target variable could be passed as R/W, we really want to identify *one* reference to the target variable to be passed as R/W, allowing any other ones to be passed read/only as they would be by default. As long as the R/W reference is a direct argument to the top-level (hence last to be executed) function in the expression, there is no harm in R/O references being passed to other lower parts of the expression. Nor is there any use-case for more than one argument of the top-level function being R/W. Hence, rewrite that logic to identify one single Param that references the target variable, and make only that Param pass a read/write reference, not any other Params referencing the target variable. Discussion: https://postgr.es/m/4165684.1607707277@sss.pgh.pa.us
1 parent 1788828 commit 1c1cbe2

File tree

4 files changed

+106
-96
lines changed

4 files changed

+106
-96
lines changed

src/backend/utils/adt/arrayfuncs.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2582,8 +2582,11 @@ array_set_element_expanded(Datum arraydatum,
25822582

25832583
/*
25842584
* Copy new element into array's context, if needed (we assume it's
2585-
* already detoasted, so no junk should be created). If we fail further
2586-
* down, this memory is leaked, but that's reasonably harmless.
2585+
* already detoasted, so no junk should be created). Doing this before
2586+
* we've made any significant changes ensures that our behavior is sane
2587+
* even when the source is a reference to some element of this same array.
2588+
* If we fail further down, this memory is leaked, but that's reasonably
2589+
* harmless.
25872590
*/
25882591
if (!eah->typbyval && !isNull)
25892592
{

src/pl/plpgsql/src/pl_exec.c

Lines changed: 87 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -333,8 +333,7 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
333333
bool keepplan);
334334
static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
335335
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);
338337
static 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
*/
81258139
static 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
/* ----------

src/pl/plpgsql/src/pl_gram.y

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2820,7 +2820,7 @@ read_sql_construct(int until,
28202820
expr->parseMode = parsemode;
28212821
expr->plan = NULL;
28222822
expr->paramnos = NULL;
2823-
expr->rwparam = -1;
2823+
expr->target_param = -1;
28242824
expr->ns = plpgsql_ns_top();
28252825
pfree(ds.data);
28262826

@@ -3067,7 +3067,7 @@ make_execsql_stmt(int firsttoken, int location)
30673067
expr->parseMode = RAW_PARSE_DEFAULT;
30683068
expr->plan = NULL;
30693069
expr->paramnos = NULL;
3070-
expr->rwparam = -1;
3070+
expr->target_param = -1;
30713071
expr->ns = plpgsql_ns_top();
30723072
pfree(ds.data);
30733073

@@ -3949,7 +3949,7 @@ read_cursor_args(PLpgSQL_var *cursor, int until)
39493949
expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
39503950
expr->plan = NULL;
39513951
expr->paramnos = NULL;
3952-
expr->rwparam = -1;
3952+
expr->target_param = -1;
39533953
expr->ns = plpgsql_ns_top();
39543954
pfree(ds.data);
39553955

src/pl/plpgsql/src/plpgsql.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,6 @@ typedef struct PLpgSQL_expr
221221
RawParseMode parseMode; /* raw_parser() mode to use */
222222
SPIPlanPtr plan; /* plan, or NULL if not made yet */
223223
Bitmapset *paramnos; /* all dnos referenced by this query */
224-
int rwparam; /* dno of read/write param, or -1 if none */
225224

226225
/* function containing this expr (not set until we first parse query) */
227226
struct PLpgSQL_function *func;
@@ -235,6 +234,17 @@ typedef struct PLpgSQL_expr
235234
int32 expr_simple_typmod; /* result typmod, if simple */
236235
bool expr_simple_mutable; /* true if simple expr is mutable */
237236

237+
/*
238+
* These fields are used to optimize assignments to expanded-datum
239+
* variables. If this expression is the source of an assignment to a
240+
* simple variable, target_param holds that variable's dno; else it's -1.
241+
* If we match a Param within expr_simple_expr to such a variable, that
242+
* Param's address is stored in expr_rw_param; then expression code
243+
* generation will allow the value for that Param to be passed read/write.
244+
*/
245+
int target_param; /* dno of assign target, or -1 if none */
246+
Param *expr_rw_param; /* read/write Param within expr, if any */
247+
238248
/*
239249
* If the expression was ever determined to be simple, we remember its
240250
* CachedPlanSource and CachedPlan here. If expr_simple_plan_lxid matches

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy