aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/include/executor/execExpr.h1
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_array.out9
-rw-r--r--src/pl/plpgsql/src/pl_exec.c383
-rw-r--r--src/pl/plpgsql/src/plpgsql.h22
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_array.sql9
-rw-r--r--src/tools/pgindent/typedefs.list2
6 files changed, 364 insertions, 62 deletions
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 51bd35dcb07..191d8fe34de 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -425,6 +425,7 @@ typedef struct ExprEvalStep
{
ExecEvalSubroutine paramfunc; /* add-on evaluation subroutine */
void *paramarg; /* private data for same */
+ void *paramarg2; /* more private data for same */
int paramid; /* numeric ID for parameter */
Oid paramtype; /* OID of parameter's datatype */
} cparam;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
index ad60e0e8be3..e5db6d60876 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_array.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -52,6 +52,15 @@ NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
do $$ declare a int[];
begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
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;
do $$ declare a int[];
begin a := f1 from onecol; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index fec1811ae10..28b6c85d8d2 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -251,6 +251,15 @@ static HTAB *shared_cast_hash = NULL;
else \
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
************************************************************/
@@ -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 bool exec_is_simple_query(PLpgSQL_expr *expr);
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 bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr,
@@ -384,6 +395,10 @@ static ParamExternData *plpgsql_param_fetch(ParamListInfo params,
static void plpgsql_param_compile(ParamListInfo params, Param *param,
ExprState *state,
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,
ExprContext *econtext);
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
- * pointers) in case we fail while replanning. expr_simple_plansource
- * can be left alone however, as that cannot move.
+ * pointers) in case we fail while replanning. We'll need to
+ * 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_rwopt = PLPGSQL_RWOPT_UNKNOWN;
expr->expr_rw_param = NULL;
expr->expr_simple_plan = NULL;
expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
@@ -6439,16 +6457,27 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
scratch.resnull = resnull;
/*
- * Select appropriate eval function. 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.
+ * Select appropriate eval function.
+ *
+ * First, if this Param references the same varlena-type DTYPE_VAR datum
+ * that is the target of the assignment containing this simple expression,
+ * 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 (param != expr->expr_rw_param &&
- ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ bool isvarlena = (((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;
else
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;
else if (datum->dtype == PLPGSQL_DTYPE_PROMISE)
{
- if (param != expr->expr_rw_param &&
- ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ if (((PLpgSQL_var *) datum)->datatype->typlen == -1)
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
else
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
}
- else if (datum->dtype == PLPGSQL_DTYPE_REC &&
- param != expr->expr_rw_param)
+ else if (datum->dtype == PLPGSQL_DTYPE_REC)
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
else
scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
@@ -6473,15 +6500,178 @@ plpgsql_param_compile(ParamListInfo params, Param *param,
* 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
* 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.paramtype = param->paramtype;
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
*
* This is specialized to the case of DTYPE_VAR variables for which
@@ -7957,9 +8147,10 @@ exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
MemoryContext oldcontext;
/*
- * Initialize to "not simple".
+ * Initialize to "not simple", and reset R/W optimizability.
*/
expr->expr_simple_expr = NULL;
+ expr->expr_rwopt = PLPGSQL_RWOPT_UNKNOWN;
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);
/* We also want to remember if it is immutable or not */
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?
*
- * 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
- * error, then when x has an expanded object as value, it is safe to pass the
- * value as a read/write pointer and let the function modify the value
- * in-place.
+ * error, then when the var has an expanded object as value, it is safe to
+ * pass the value as a read/write pointer to the top-level function and let
+ * 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
- * to the address of any 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.
+ * This function checks to see if the assignment is optimizable according
+ * to either rule, and updates expr->expr_rwopt accordingly. In addition,
+ * 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
- * one Param; the expression could contain other Params referencing the target
- * variable, but those must still be treated as read-only.
+ * Note that this mechanism intentionally allows just one Param to emit a
+ * read/write pointer; in case 2, the expression could contain other Params
+ * referencing the target variable, but those must be treated as read-only.
*
* Also note that we only apply this optimization within simple expressions.
* There's no point in it for non-simple expressions, because the
* 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
-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;
List *fargs;
ListCell *lc;
/* Assume unsafe */
+ expr->expr_rwopt = PLPGSQL_RWOPT_NOPE;
expr->expr_rw_param = NULL;
- /* Done if expression isn't an assignment source */
- target_dno = expr->target_param;
- if (target_dno < 0)
- return;
+ /* Shouldn't be here for non-simple expression */
+ Assert(sexpr != NULL);
+
+ /* Param should match the expression's assignment target, too */
+ Assert(paramid == expr->target_param + 1);
/*
- * If target variable isn't referenced by expression, no need to look
- * further.
+ * If the assignment is to a "local" variable (one whose value won't
+ * 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 (!bms_is_member(target_dno, expr->paramnos))
- return;
+ if (expr->target_is_local)
+ {
+ count_param_references_context context;
- /* Shouldn't be here for non-simple expression */
- Assert(expr->expr_simple_expr != NULL);
+ /* 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 optimize.
+ * 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(expr->expr_simple_expr, FuncExpr))
+ if (IsA(sexpr, RelabelType))
+ sexpr = ((RelabelType *) sexpr)->arg;
+ if (IsA(sexpr, FuncExpr))
{
- FuncExpr *fexpr = (FuncExpr *) expr->expr_simple_expr;
+ FuncExpr *fexpr = (FuncExpr *) sexpr;
funcid = fexpr->funcid;
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;
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 */
+ /* TODO: install some extensibility here */
if (get_typsubscript(sbsref->refcontainertype, NULL) !=
F_ARRAY_SUBSCRIPT_HANDLER)
return;
@@ -8256,9 +8492,10 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
Param *param = (Param *) sbsref->refexpr;
if (param->paramkind == PARAM_EXTERN &&
- param->paramid == target_dno + 1)
+ param->paramid == paramid)
{
/* Found the Param we want to pass as read/write */
+ expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
expr->expr_rw_param = param;
return;
}
@@ -8293,9 +8530,10 @@ exec_check_rw_parameter(PLpgSQL_expr *expr)
Param *param = (Param *) arg;
if (param->paramkind == PARAM_EXTERN &&
- param->paramid == target_dno + 1)
+ param->paramid == paramid)
{
/* Found the Param we want to pass as read/write */
+ expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
expr->expr_rw_param = param;
return;
}
@@ -8304,6 +8542,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?
*
* This should match pl_gram.y's check_assignable().
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index 2fa6d73cabc..d73996e09c0 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -187,6 +187,17 @@ typedef enum PLpgSQL_resolve_option
PLPGSQL_RESOLVE_COLUMN, /* prefer table column to plpgsql var */
} 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
@@ -246,11 +257,14 @@ typedef struct PLpgSQL_expr
bool expr_simple_mutable; /* true if simple expr is mutable */
/*
- * If we match a Param within expr_simple_expr to the variable identified
- * by target_param, that Param's address is stored in expr_rw_param; then
- * expression code generation will allow the value for that Param to be
- * passed as a read/write expanded-object pointer.
+ * expr_rwopt tracks whether we have determined that assignment to a
+ * read/write expanded object (stored in the target_param datum) can be
+ * optimized by passing it to the expr as a read/write expanded-object
+ * 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 */
/*
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
index 4b9ff515948..4a346203dc2 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_array.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -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[];
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;
do $$ declare a int[];
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 656ecd919de..110aa8ab5a2 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1873,6 +1873,7 @@ PLpgSQL_rec
PLpgSQL_recfield
PLpgSQL_resolve_option
PLpgSQL_row
+PLpgSQL_rwopt
PLpgSQL_stmt
PLpgSQL_stmt_assert
PLpgSQL_stmt_assign
@@ -3414,6 +3415,7 @@ core_yy_extra_type
core_yyscan_t
corrupt_items
cost_qual_eval_context
+count_param_references_context
cp_hash_func
create_upper_paths_hook_type
createdb_failure_params