1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

Implement new optimization rule for updates of expanded variables.

If a read/write expanded variable is declared locally to the
assignment statement that is updating it, and it is referenced
exactly once in the assignment RHS, then we can optimize the
operation as a direct update of the expanded value, whether
or not the function(s) operating on it can be trusted not to
modify the value before throwing an error.  This works because
if an error does get thrown, we no longer care what value the
variable has.

In cases where that doesn't work, fall back to the previous
rule that checks for safety of the top-level function.

In any case, postpone determination of whether these optimizations
are feasible until we are executing a Param referencing the target
variable and that variable holds a R/W expanded object.  While the
previous incarnation of exec_check_rw_parameter was pretty cheap,
this is a bit less so, and our plan to invoke support functions
will make it even less so.  So avoiding the check for variables
where it couldn't be useful should be a win.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Andrey Borodin <x4mmm@yandex-team.ru>
Reviewed-by: Pavel Borisov <pashkin.elfe@gmail.com>
Discussion: https://postgr.es/m/CACxu=vJaKFNsYxooSnW1wEgsAO5u_v1XYBacfVJ14wgJV_PYeg@mail.gmail.com
This commit is contained in:
Tom Lane
2025-02-11 12:34:59 -05:00
parent 36fb9ef269
commit 6c7251db0c
6 changed files with 368 additions and 66 deletions

View File

@ -425,6 +425,7 @@ typedef struct ExprEvalStep
{ {
ExecEvalSubroutine paramfunc; /* add-on evaluation subroutine */ ExecEvalSubroutine paramfunc; /* add-on evaluation subroutine */
void *paramarg; /* private data for same */ void *paramarg; /* private data for same */
void *paramarg2; /* more private data for same */
int paramid; /* numeric ID for parameter */ int paramid; /* numeric ID for parameter */
Oid paramtype; /* OID of parameter's datatype */ Oid paramtype; /* OID of parameter's datatype */
} cparam; } cparam;

View File

@ -52,6 +52,15 @@ NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[]; do $$ declare a int[];
begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$; begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
NOTICE: a = {1,2,3} NOTICE: a = {1,2,3}
do $$ declare a int[] := array[1,2,3];
begin
-- test scenarios for optimization of updates of R/W expanded objects
a := array_append(a, 42); -- optimizable using "transfer" method
a := a || a[3]; -- optimizable using "inplace" method
a := a || a; -- not optimizable
raise notice 'a = %', a;
end$$;
NOTICE: a = {1,2,3,42,3,1,2,3,42,3}
create temp table onecol as select array[1,2] as f1; create temp table onecol as select array[1,2] as f1;
do $$ declare a int[]; do $$ declare a int[];
begin a := f1 from onecol; raise notice 'a = %', a; end$$; begin a := f1 from onecol; raise notice 'a = %', a; end$$;

View File

@ -251,6 +251,15 @@ static HTAB *shared_cast_hash = NULL;
else \ else \
Assert(rc == PLPGSQL_RC_OK) Assert(rc == PLPGSQL_RC_OK)
/* State struct for count_param_references */
typedef struct count_param_references_context
{
int paramid;
int count;
Param *last_param;
} count_param_references_context;
/************************************************************ /************************************************************
* Local function forward declarations * Local function forward declarations
************************************************************/ ************************************************************/
@ -336,7 +345,9 @@ static void exec_prepare_plan(PLpgSQL_execstate *estate,
static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr); static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
static bool exec_is_simple_query(PLpgSQL_expr *expr); static bool exec_is_simple_query(PLpgSQL_expr *expr);
static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan); static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
static void exec_check_rw_parameter(PLpgSQL_expr *expr); static void exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid);
static bool count_param_references(Node *node,
count_param_references_context *context);
static void exec_check_assignable(PLpgSQL_execstate *estate, int dno); static void exec_check_assignable(PLpgSQL_execstate *estate, int dno);
static bool exec_eval_simple_expr(PLpgSQL_execstate *estate, static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr, PLpgSQL_expr *expr,
@ -384,6 +395,10 @@ static ParamExternData *plpgsql_param_fetch(ParamListInfo params,
static void plpgsql_param_compile(ParamListInfo params, Param *param, static void plpgsql_param_compile(ParamListInfo params, Param *param,
ExprState *state, ExprState *state,
Datum *resv, bool *resnull); Datum *resv, bool *resnull);
static void plpgsql_param_eval_var_check(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
static void plpgsql_param_eval_var_transfer(ExprState *state, ExprEvalStep *op,
ExprContext *econtext);
static void plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op, static void plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
ExprContext *econtext); ExprContext *econtext);
static void plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op, static void plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
@ -6078,10 +6093,13 @@ exec_eval_simple_expr(PLpgSQL_execstate *estate,
/* /*
* Reset to "not simple" to leave sane state (with no dangling * Reset to "not simple" to leave sane state (with no dangling
* pointers) in case we fail while replanning. expr_simple_plansource * pointers) in case we fail while replanning. We'll need to
* can be left alone however, as that cannot move. * re-determine simplicity and R/W optimizability anyway, since those
* could change with the new plan. expr_simple_plansource can be left
* alone however, as that cannot move.
*/ */
expr->expr_simple_expr = NULL; expr->expr_simple_expr = NULL;
expr->expr_rwopt = PLPGSQL_RWOPT_UNKNOWN;
expr->expr_rw_param = NULL; expr->expr_rw_param = NULL;
expr->expr_simple_plan = NULL; expr->expr_simple_plan = NULL;
expr->expr_simple_plan_lxid = InvalidLocalTransactionId; expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
@ -6439,16 +6457,27 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
scratch.resnull = resnull; scratch.resnull = resnull;
/* /*
* Select appropriate eval function. It seems worth special-casing * Select appropriate eval function.
* DTYPE_VAR and DTYPE_RECFIELD for performance. Also, we can determine *
* in advance whether MakeExpandedObjectReadOnly() will be required. * First, if this Param references the same varlena-type DTYPE_VAR datum
* Currently, only VAR/PROMISE and REC datums could contain read/write * that is the target of the assignment containing this simple expression,
* expanded objects. * then it's possible we will be able to optimize handling of R/W expanded
* datums. We don't want to do the work needed to determine that unless
* we actually see a R/W expanded datum at runtime, so install a checking
* function that will figure that out when needed.
*
* Otherwise, it seems worth special-casing DTYPE_VAR and DTYPE_RECFIELD
* for performance. Also, we can determine in advance whether
* MakeExpandedObjectReadOnly() will be required. Currently, only
* VAR/PROMISE and REC datums could contain read/write expanded objects.
*/ */
if (datum->dtype == PLPGSQL_DTYPE_VAR) if (datum->dtype == PLPGSQL_DTYPE_VAR)
{ {
if (param != expr->expr_rw_param && bool isvarlena = (((PLpgSQL_var *) datum)->datatype->typlen == -1);
((PLpgSQL_var *) datum)->datatype->typlen == -1)
if (isvarlena && dno == expr->target_param && expr->expr_simple_expr)
scratch.d.cparam.paramfunc = plpgsql_param_eval_var_check;
else if (isvarlena)
scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro; scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro;
else else
scratch.d.cparam.paramfunc = plpgsql_param_eval_var; scratch.d.cparam.paramfunc = plpgsql_param_eval_var;
@ -6457,14 +6486,12 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
scratch.d.cparam.paramfunc = plpgsql_param_eval_recfield; scratch.d.cparam.paramfunc = plpgsql_param_eval_recfield;
else if (datum->dtype == PLPGSQL_DTYPE_PROMISE) else if (datum->dtype == PLPGSQL_DTYPE_PROMISE)
{ {
if (param != expr->expr_rw_param && if (((PLpgSQL_var *) datum)->datatype->typlen == -1)
((PLpgSQL_var *) datum)->datatype->typlen == -1)
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro; scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
else else
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic; scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
} }
else if (datum->dtype == PLPGSQL_DTYPE_REC && else if (datum->dtype == PLPGSQL_DTYPE_REC)
param != expr->expr_rw_param)
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro; scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
else else
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic; scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
@ -6473,14 +6500,177 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
* Note: it's tempting to use paramarg to store the estate pointer and * Note: it's tempting to use paramarg to store the estate pointer and
* thereby save an indirection or two in the eval functions. But that * thereby save an indirection or two in the eval functions. But that
* doesn't work because the compiled expression might be used with * doesn't work because the compiled expression might be used with
* different estates for the same PL/pgSQL function. * different estates for the same PL/pgSQL function. Instead, store
* pointers to the PLpgSQL_expr as well as this specific Param, to support
* plpgsql_param_eval_var_check().
*/ */
scratch.d.cparam.paramarg = NULL; scratch.d.cparam.paramarg = expr;
scratch.d.cparam.paramarg2 = param;
scratch.d.cparam.paramid = param->paramid; scratch.d.cparam.paramid = param->paramid;
scratch.d.cparam.paramtype = param->paramtype; scratch.d.cparam.paramtype = param->paramtype;
ExprEvalPushStep(state, &scratch); ExprEvalPushStep(state, &scratch);
} }
/*
* plpgsql_param_eval_var_check evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we may need to determine the applicability of a read/write optimization,
* but we've not done that yet. The work to determine applicability will
* be done at most once (per construction of the PL/pgSQL function's cache
* entry) when we first see that the target variable's old value is a R/W
* expanded object. If we never do see that, nothing is lost: the amount
* of work done by this function in that case is just about the same as
* what would be done by plpgsql_param_eval_var_ro, which is what we'd
* have used otherwise.
*/
static void
plpgsql_param_eval_var_check(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/*
* If the variable's current value is a R/W expanded object, it's time to
* decide whether/how to optimize the assignment.
*/
if (!var->isnull &&
VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
{
PLpgSQL_expr *expr = (PLpgSQL_expr *) op->d.cparam.paramarg;
Param *param = (Param *) op->d.cparam.paramarg2;
/*
* We might have already figured this out while evaluating some other
* Param referencing the same variable, so check expr_rwopt first.
*/
if (expr->expr_rwopt == PLPGSQL_RWOPT_UNKNOWN)
exec_check_rw_parameter(expr, op->d.cparam.paramid);
/*
* Update the callback pointer to match what we decided to do, so that
* this function will not be called again. Then pass off this
* execution to the newly-selected function.
*/
switch (expr->expr_rwopt)
{
case PLPGSQL_RWOPT_UNKNOWN:
Assert(false);
break;
case PLPGSQL_RWOPT_NOPE:
/* Force the value to read-only in all future executions */
op->d.cparam.paramfunc = plpgsql_param_eval_var_ro;
plpgsql_param_eval_var_ro(state, op, econtext);
break;
case PLPGSQL_RWOPT_TRANSFER:
/* There can be only one matching Param in this case */
Assert(param == expr->expr_rw_param);
/* When the value is read/write, transfer to exec context */
op->d.cparam.paramfunc = plpgsql_param_eval_var_transfer;
plpgsql_param_eval_var_transfer(state, op, econtext);
break;
case PLPGSQL_RWOPT_INPLACE:
if (param == expr->expr_rw_param)
{
/* When the value is read/write, deliver it as-is */
op->d.cparam.paramfunc = plpgsql_param_eval_var;
plpgsql_param_eval_var(state, op, econtext);
}
else
{
/* Not the optimizable reference, so force to read-only */
op->d.cparam.paramfunc = plpgsql_param_eval_var_ro;
plpgsql_param_eval_var_ro(state, op, econtext);
}
break;
}
return;
}
/*
* Otherwise, continue to postpone that decision, and execute an inlined
* version of exec_eval_datum(). Although this value could potentially
* need MakeExpandedObjectReadOnly, we know it doesn't right now.
*/
*op->resvalue = var->value;
*op->resnull = var->isnull;
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/*
* plpgsql_param_eval_var_transfer evaluation of EEOP_PARAM_CALLBACK step
*
* This is specialized to the case of DTYPE_VAR variables for which
* we have determined that a read/write expanded value can be handed off
* into execution of the expression (and then possibly returned to our
* function's ownership afterwards). We have to test though, because the
* variable might not contain a read/write expanded value during this
* execution.
*/
static void
plpgsql_param_eval_var_transfer(ExprState *state, ExprEvalStep *op,
ExprContext *econtext)
{
ParamListInfo params;
PLpgSQL_execstate *estate;
int dno = op->d.cparam.paramid - 1;
PLpgSQL_var *var;
/* fetch back the hook data */
params = econtext->ecxt_param_list_info;
estate = (PLpgSQL_execstate *) params->paramFetchArg;
Assert(dno >= 0 && dno < estate->ndatums);
/* now we can access the target datum */
var = (PLpgSQL_var *) estate->datums[dno];
Assert(var->dtype == PLPGSQL_DTYPE_VAR);
/*
* If the variable's current value is a R/W expanded object, transfer its
* ownership into the expression execution context, then drop our own
* reference to the value by setting the variable to NULL. That'll be
* overwritten (perhaps with this same object) when control comes back
* from the expression.
*/
if (!var->isnull &&
VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
{
*op->resvalue = TransferExpandedObject(var->value,
get_eval_mcontext(estate));
*op->resnull = false;
var->value = (Datum) 0;
var->isnull = true;
var->freeval = false;
}
else
{
/*
* Otherwise we can pass the variable's value directly; we now know
* that MakeExpandedObjectReadOnly isn't needed.
*/
*op->resvalue = var->value;
*op->resnull = var->isnull;
}
/* safety check -- an assertion should be sufficient */
Assert(var->datatype->typoid == op->d.cparam.paramtype);
}
/* /*
* plpgsql_param_eval_var evaluation of EEOP_PARAM_CALLBACK step * plpgsql_param_eval_var evaluation of EEOP_PARAM_CALLBACK step
* *
@ -7957,9 +8147,10 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
MemoryContext oldcontext; MemoryContext oldcontext;
/* /*
* Initialize to "not simple". * Initialize to "not simple", and reset R/W optimizability.
*/ */
expr->expr_simple_expr = NULL; expr->expr_simple_expr = NULL;
expr->expr_rwopt = PLPGSQL_RWOPT_UNKNOWN;
expr->expr_rw_param = NULL; expr->expr_rw_param = NULL;
/* /*
@ -8164,88 +8355,133 @@ exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
expr->expr_simple_typmod = exprTypmod((Node *) tle_expr); expr->expr_simple_typmod = exprTypmod((Node *) tle_expr);
/* We also want to remember if it is immutable or not */ /* We also want to remember if it is immutable or not */
expr->expr_simple_mutable = contain_mutable_functions((Node *) tle_expr); expr->expr_simple_mutable = contain_mutable_functions((Node *) tle_expr);
/*
* Lastly, check to see if there's a possibility of optimizing a
* read/write parameter.
*/
exec_check_rw_parameter(expr);
} }
/* /*
* exec_check_rw_parameter --- can we pass expanded object as read/write param? * exec_check_rw_parameter --- can we pass expanded object as read/write param?
* *
* If we have an assignment like "x := array_append(x, foo)" in which the * There are two separate cases in which we can optimize an update to a
* variable that has a read/write expanded value by letting the called
* expression operate directly on the expanded value. In both cases we
* are considering assignments like "var := array_append(var, foo)" where
* the assignment target is also an input to the RHS expression.
*
* Case 1 (RWOPT_TRANSFER rule): if the variable is "local" in the sense that
* its declaration is not outside any BEGIN...EXCEPTION block surrounding the
* assignment, then we do not need to worry about preserving its value if the
* RHS expression throws an error. If in addition the variable is referenced
* exactly once in the RHS expression, then we can optimize by converting the
* read/write expanded value into a transient value within the expression
* evaluation context, and then setting the variable's recorded value to NULL
* to prevent double-free attempts. This works regardless of any other
* details of the RHS expression. If the expression eventually returns that
* same expanded object (possibly modified) then the variable will re-acquire
* ownership; while if it returns something else or throws an error, the
* expanded object will be discarded as part of cleanup of the evaluation
* context.
*
* Case 2 (RWOPT_INPLACE rule): if we have a non-local assignment or if
* it looks like "var := array_append(var, var[1])" with multiple references
* to the target variable, then we can't use case 1. Nonetheless, if the
* top-level function is trusted not to corrupt its argument in case of an * top-level function is trusted not to corrupt its argument in case of an
* error, then when x has an expanded object as value, it is safe to pass the * error, then when the var has an expanded object as value, it is safe to
* value as a read/write pointer and let the function modify the value * pass the value as a read/write pointer to the top-level function and let
* in-place. * the function modify the value in-place. (Any other references have to be
* passed as read-only pointers as usual.) Only the top-level function has to
* be trusted, since if anything further down fails, the object hasn't been
* modified yet.
* *
* This function checks for a safe expression, and sets expr->expr_rw_param * This function checks to see if the assignment is optimizable according
* to the address of any Param within the expression that can be passed as * to either rule, and updates expr->expr_rwopt accordingly. In addition,
* read/write (there can be only one); or to NULL when there is no safe Param. * it sets expr->expr_rw_param to the address of the Param within the
* expression that can be passed as read/write (there can be only one);
* or to NULL when there is no safe Param.
* *
* Note that this mechanism intentionally applies the safety labeling to just * Note that this mechanism intentionally allows just one Param to emit a
* one Param; the expression could contain other Params referencing the target * read/write pointer; in case 2, the expression could contain other Params
* variable, but those must still be treated as read-only. * referencing the target variable, but those must be treated as read-only.
* *
* Also note that we only apply this optimization within simple expressions. * Also note that we only apply this optimization within simple expressions.
* There's no point in it for non-simple expressions, because the * There's no point in it for non-simple expressions, because the
* exec_run_select code path will flatten any expanded result anyway. * exec_run_select code path will flatten any expanded result anyway.
* Also, it's safe to assume that an expr_simple_expr tree won't get copied
* somewhere before it gets compiled, so that looking for pointer equality
* to expr_rw_param will work for matching the target Param. That'd be much
* shakier in the general case.
*/ */
static void static void
exec_check_rw_parameter(PLpgSQL_expr *expr) exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid)
{ {
int target_dno; Expr *sexpr = expr->expr_simple_expr;
Oid funcid; Oid funcid;
List *fargs; List *fargs;
ListCell *lc; ListCell *lc;
/* Assume unsafe */ /* Assume unsafe */
expr->expr_rwopt = PLPGSQL_RWOPT_NOPE;
expr->expr_rw_param = NULL; expr->expr_rw_param = NULL;
/* Done if expression isn't an assignment source */
target_dno = expr->target_param;
if (target_dno < 0)
return;
/*
* If target variable isn't referenced by expression, no need to look
* further.
*/
if (!bms_is_member(target_dno, expr->paramnos))
return;
/* Shouldn't be here for non-simple expression */ /* Shouldn't be here for non-simple expression */
Assert(expr->expr_simple_expr != NULL); Assert(sexpr != NULL);
/* Param should match the expression's assignment target, too */
Assert(paramid == expr->target_param + 1);
/* /*
* Top level of expression must be a simple FuncExpr, OpExpr, or * If the assignment is to a "local" variable (one whose value won't
* SubscriptingRef, else we can't optimize. * matter anymore if expression evaluation fails), and this Param is the
* only reference to that variable in the expression, then we can
* unconditionally optimize using the "transfer" method.
*/ */
if (IsA(expr->expr_simple_expr, FuncExpr)) if (expr->target_is_local)
{ {
FuncExpr *fexpr = (FuncExpr *) expr->expr_simple_expr; count_param_references_context context;
/* See how many references there are, and find one of them */
context.paramid = paramid;
context.count = 0;
context.last_param = NULL;
(void) count_param_references((Node *) sexpr, &context);
/* If we're here, the expr must contain some reference to the var */
Assert(context.count > 0);
/* If exactly one reference, success! */
if (context.count == 1)
{
expr->expr_rwopt = PLPGSQL_RWOPT_TRANSFER;
expr->expr_rw_param = context.last_param;
return;
}
}
/*
* Otherwise, see if we can trust the expression's top-level function to
* apply the "inplace" method.
*
* Top level of expression must be a simple FuncExpr, OpExpr, or
* SubscriptingRef, else we can't identify which function is relevant. But
* it's okay to look through any RelabelType above that, since that can't
* fail.
*/
if (IsA(sexpr, RelabelType))
sexpr = ((RelabelType *) sexpr)->arg;
if (IsA(sexpr, FuncExpr))
{
FuncExpr *fexpr = (FuncExpr *) sexpr;
funcid = fexpr->funcid; funcid = fexpr->funcid;
fargs = fexpr->args; fargs = fexpr->args;
} }
else if (IsA(expr->expr_simple_expr, OpExpr)) else if (IsA(sexpr, OpExpr))
{ {
OpExpr *opexpr = (OpExpr *) expr->expr_simple_expr; OpExpr *opexpr = (OpExpr *) sexpr;
funcid = opexpr->opfuncid; funcid = opexpr->opfuncid;
fargs = opexpr->args; fargs = opexpr->args;
} }
else if (IsA(expr->expr_simple_expr, SubscriptingRef)) else if (IsA(sexpr, SubscriptingRef))
{ {
SubscriptingRef *sbsref = (SubscriptingRef *) expr->expr_simple_expr; SubscriptingRef *sbsref = (SubscriptingRef *) sexpr;
/* We only trust standard varlena arrays to be safe */ /* We only trust standard varlena arrays to be safe */
/* TODO: install some extensibility here */
if (get_typsubscript(sbsref->refcontainertype, NULL) != if (get_typsubscript(sbsref->refcontainertype, NULL) !=
F_ARRAY_SUBSCRIPT_HANDLER) F_ARRAY_SUBSCRIPT_HANDLER)
return; return;
@ -8256,9 +8492,10 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
Param *param = (Param *) sbsref->refexpr; Param *param = (Param *) sbsref->refexpr;
if (param->paramkind == PARAM_EXTERN && if (param->paramkind == PARAM_EXTERN &&
param->paramid == target_dno + 1) param->paramid == paramid)
{ {
/* Found the Param we want to pass as read/write */ /* Found the Param we want to pass as read/write */
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
expr->expr_rw_param = param; expr->expr_rw_param = param;
return; return;
} }
@ -8293,9 +8530,10 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
Param *param = (Param *) arg; Param *param = (Param *) arg;
if (param->paramkind == PARAM_EXTERN && if (param->paramkind == PARAM_EXTERN &&
param->paramid == target_dno + 1) param->paramid == paramid)
{ {
/* Found the Param we want to pass as read/write */ /* Found the Param we want to pass as read/write */
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
expr->expr_rw_param = param; expr->expr_rw_param = param;
return; return;
} }
@ -8303,6 +8541,35 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
} }
} }
/*
* Count Params referencing the specified paramid, and return one of them
* if there are any.
*
* We actually only need to distinguish 0, 1, and N references; so we can
* abort the tree traversal as soon as we've found two.
*/
static bool
count_param_references(Node *node, count_param_references_context *context)
{
if (node == NULL)
return false;
else if (IsA(node, Param))
{
Param *param = (Param *) node;
if (param->paramkind == PARAM_EXTERN &&
param->paramid == context->paramid)
{
context->last_param = param;
if (++(context->count) > 1)
return true; /* abort tree traversal */
}
return false;
}
else
return expression_tree_walker(node, count_param_references, context);
}
/* /*
* exec_check_assignable --- is it OK to assign to the indicated datum? * exec_check_assignable --- is it OK to assign to the indicated datum?
* *

View File

@ -187,6 +187,17 @@ typedef enum PLpgSQL_resolve_option
PLPGSQL_RESOLVE_COLUMN, /* prefer table column to plpgsql var */ PLPGSQL_RESOLVE_COLUMN, /* prefer table column to plpgsql var */
} PLpgSQL_resolve_option; } PLpgSQL_resolve_option;
/*
* Status of optimization of assignment to a read/write expanded object
*/
typedef enum PLpgSQL_rwopt
{
PLPGSQL_RWOPT_UNKNOWN = 0, /* applicability not determined yet */
PLPGSQL_RWOPT_NOPE, /* cannot do any optimization */
PLPGSQL_RWOPT_TRANSFER, /* transfer the old value into expr state */
PLPGSQL_RWOPT_INPLACE, /* pass value as R/W to top-level function */
} PLpgSQL_rwopt;
/********************************************************************** /**********************************************************************
* Node and structure definitions * Node and structure definitions
@ -246,11 +257,14 @@ typedef struct PLpgSQL_expr
bool expr_simple_mutable; /* true if simple expr is mutable */ bool expr_simple_mutable; /* true if simple expr is mutable */
/* /*
* If we match a Param within expr_simple_expr to the variable identified * expr_rwopt tracks whether we have determined that assignment to a
* by target_param, that Param's address is stored in expr_rw_param; then * read/write expanded object (stored in the target_param datum) can be
* expression code generation will allow the value for that Param to be * optimized by passing it to the expr as a read/write expanded-object
* passed as a read/write expanded-object pointer. * pointer. If so, expr_rw_param identifies the specific Param that
* should emit a read/write pointer; any others will emit read-only
* pointers.
*/ */
PLpgSQL_rwopt expr_rwopt; /* can we apply R/W optimization? */
Param *expr_rw_param; /* read/write Param within expr, if any */ Param *expr_rw_param; /* read/write Param within expr, if any */
/* /*

View File

@ -48,6 +48,15 @@ begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
do $$ declare a int[]; do $$ declare a int[];
begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$; begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
do $$ declare a int[] := array[1,2,3];
begin
-- test scenarios for optimization of updates of R/W expanded objects
a := array_append(a, 42); -- optimizable using "transfer" method
a := a || a[3]; -- optimizable using "inplace" method
a := a || a; -- not optimizable
raise notice 'a = %', a;
end$$;
create temp table onecol as select array[1,2] as f1; create temp table onecol as select array[1,2] as f1;
do $$ declare a int[]; do $$ declare a int[];

View File

@ -1873,6 +1873,7 @@ PLpgSQL_rec
PLpgSQL_recfield PLpgSQL_recfield
PLpgSQL_resolve_option PLpgSQL_resolve_option
PLpgSQL_row PLpgSQL_row
PLpgSQL_rwopt
PLpgSQL_stmt PLpgSQL_stmt
PLpgSQL_stmt_assert PLpgSQL_stmt_assert
PLpgSQL_stmt_assign PLpgSQL_stmt_assign
@ -3414,6 +3415,7 @@ core_yy_extra_type
core_yyscan_t core_yyscan_t
corrupt_items corrupt_items
cost_qual_eval_context cost_qual_eval_context
count_param_references_context
cp_hash_func cp_hash_func
create_upper_paths_hook_type create_upper_paths_hook_type
createdb_failure_params createdb_failure_params