Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit c366d2b

Browse files
committed
Allow extension functions to participate in in-place updates.
Commit 1dc5ebc allowed PL/pgSQL to perform in-place updates of expanded-object variables that are being updated with assignments like "x := f(x, ...)". However this was allowed only for a hard-wired list of functions f(), since we need to be sure that f() will not modify the variable if it fails. It was always envisioned that we should make that extensible, but at the time we didn't have a good way to do so. Since then we've invented the idea of "support functions" to allow attaching specialized optimization knowledge to functions, and that is a perfect mechanism for doing this. Hence, adjust PL/pgSQL to use a support function request instead of hard-wired logic to decide if in-place update is safe. Preserve the previous optimizations by creating support functions for the three functions that were previously hard-wired. 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
1 parent 6c7251d commit c366d2b

File tree

9 files changed

+203
-60
lines changed

9 files changed

+203
-60
lines changed

src/backend/utils/adt/array_userfuncs.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "common/int.h"
1717
#include "common/pg_prng.h"
1818
#include "libpq/pqformat.h"
19+
#include "nodes/supportnodes.h"
1920
#include "port/pg_bitutils.h"
2021
#include "utils/array.h"
2122
#include "utils/builtins.h"
@@ -167,6 +168,36 @@ array_append(PG_FUNCTION_ARGS)
167168
PG_RETURN_DATUM(result);
168169
}
169170

171+
/*
172+
* array_append_support()
173+
*
174+
* Planner support function for array_append()
175+
*/
176+
Datum
177+
array_append_support(PG_FUNCTION_ARGS)
178+
{
179+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
180+
Node *ret = NULL;
181+
182+
if (IsA(rawreq, SupportRequestModifyInPlace))
183+
{
184+
/*
185+
* We can optimize in-place appends if the function's array argument
186+
* is the array being assigned to. We don't need to worry about array
187+
* references within the other argument.
188+
*/
189+
SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
190+
Param *arg = (Param *) linitial(req->args);
191+
192+
if (arg && IsA(arg, Param) &&
193+
arg->paramkind == PARAM_EXTERN &&
194+
arg->paramid == req->paramid)
195+
ret = (Node *) arg;
196+
}
197+
198+
PG_RETURN_POINTER(ret);
199+
}
200+
170201
/*-----------------------------------------------------------------------------
171202
* array_prepend :
172203
* push an element onto the front of a one-dimensional array
@@ -230,6 +261,36 @@ array_prepend(PG_FUNCTION_ARGS)
230261
PG_RETURN_DATUM(result);
231262
}
232263

264+
/*
265+
* array_prepend_support()
266+
*
267+
* Planner support function for array_prepend()
268+
*/
269+
Datum
270+
array_prepend_support(PG_FUNCTION_ARGS)
271+
{
272+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
273+
Node *ret = NULL;
274+
275+
if (IsA(rawreq, SupportRequestModifyInPlace))
276+
{
277+
/*
278+
* We can optimize in-place prepends if the function's array argument
279+
* is the array being assigned to. We don't need to worry about array
280+
* references within the other argument.
281+
*/
282+
SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
283+
Param *arg = (Param *) lsecond(req->args);
284+
285+
if (arg && IsA(arg, Param) &&
286+
arg->paramkind == PARAM_EXTERN &&
287+
arg->paramid == req->paramid)
288+
ret = (Node *) arg;
289+
}
290+
291+
PG_RETURN_POINTER(ret);
292+
}
293+
233294
/*-----------------------------------------------------------------------------
234295
* array_cat :
235296
* concatenate two nD arrays to form an nD array, or

src/backend/utils/adt/arraysubs.c

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "nodes/makefuncs.h"
1919
#include "nodes/nodeFuncs.h"
2020
#include "nodes/subscripting.h"
21+
#include "nodes/supportnodes.h"
2122
#include "parser/parse_coerce.h"
2223
#include "parser/parse_expr.h"
2324
#include "utils/array.h"
@@ -575,3 +576,36 @@ raw_array_subscript_handler(PG_FUNCTION_ARGS)
575576

576577
PG_RETURN_POINTER(&sbsroutines);
577578
}
579+
580+
/*
581+
* array_subscript_handler_support()
582+
*
583+
* Planner support function for array_subscript_handler()
584+
*/
585+
Datum
586+
array_subscript_handler_support(PG_FUNCTION_ARGS)
587+
{
588+
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
589+
Node *ret = NULL;
590+
591+
if (IsA(rawreq, SupportRequestModifyInPlace))
592+
{
593+
/*
594+
* We can optimize in-place subscripted assignment if the refexpr is
595+
* the array being assigned to. We don't need to worry about array
596+
* references within the refassgnexpr or the subscripts; however, if
597+
* there's no refassgnexpr then it's a fetch which there's no need to
598+
* optimize.
599+
*/
600+
SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq;
601+
Param *refexpr = (Param *) linitial(req->args);
602+
603+
if (refexpr && IsA(refexpr, Param) &&
604+
refexpr->paramkind == PARAM_EXTERN &&
605+
refexpr->paramid == req->paramid &&
606+
lsecond(req->args) != NULL)
607+
ret = (Node *) refexpr;
608+
}
609+
610+
PG_RETURN_POINTER(ret);
611+
}

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202502071
60+
#define CATALOG_VERSION_NO 202502111
6161

6262
#endif

src/include/catalog/pg_proc.dat

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1598,14 +1598,20 @@
15981598
proname => 'cardinality', prorettype => 'int4', proargtypes => 'anyarray',
15991599
prosrc => 'array_cardinality' },
16001600
{ oid => '378', descr => 'append element onto end of array',
1601-
proname => 'array_append', proisstrict => 'f',
1602-
prorettype => 'anycompatiblearray',
1601+
proname => 'array_append', prosupport => 'array_append_support',
1602+
proisstrict => 'f', prorettype => 'anycompatiblearray',
16031603
proargtypes => 'anycompatiblearray anycompatible', prosrc => 'array_append' },
1604+
{ oid => '8680', descr => 'planner support for array_append',
1605+
proname => 'array_append_support', prorettype => 'internal',
1606+
proargtypes => 'internal', prosrc => 'array_append_support' },
16041607
{ oid => '379', descr => 'prepend element onto front of array',
1605-
proname => 'array_prepend', proisstrict => 'f',
1606-
prorettype => 'anycompatiblearray',
1608+
proname => 'array_prepend', prosupport => 'array_prepend_support',
1609+
proisstrict => 'f', prorettype => 'anycompatiblearray',
16071610
proargtypes => 'anycompatible anycompatiblearray',
16081611
prosrc => 'array_prepend' },
1612+
{ oid => '8681', descr => 'planner support for array_prepend',
1613+
proname => 'array_prepend_support', prorettype => 'internal',
1614+
proargtypes => 'internal', prosrc => 'array_prepend_support' },
16091615
{ oid => '383',
16101616
proname => 'array_cat', proisstrict => 'f',
16111617
prorettype => 'anycompatiblearray',
@@ -12207,8 +12213,12 @@
1220712213

1220812214
# subscripting support for built-in types
1220912215
{ oid => '6179', descr => 'standard array subscripting support',
12210-
proname => 'array_subscript_handler', prorettype => 'internal',
12216+
proname => 'array_subscript_handler',
12217+
prosupport => 'array_subscript_handler_support', prorettype => 'internal',
1221112218
proargtypes => 'internal', prosrc => 'array_subscript_handler' },
12219+
{ oid => '8682', descr => 'planner support for array_subscript_handler',
12220+
proname => 'array_subscript_handler_support', prorettype => 'internal',
12221+
proargtypes => 'internal', prosrc => 'array_subscript_handler_support' },
1221212222
{ oid => '6180', descr => 'raw array subscripting support',
1221312223
proname => 'raw_array_subscript_handler', prorettype => 'internal',
1221412224
proargtypes => 'internal', prosrc => 'raw_array_subscript_handler' },

src/include/nodes/supportnodes.h

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
* This file defines the API for "planner support functions", which
77
* are SQL functions (normally written in C) that can be attached to
88
* another "target" function to give the system additional knowledge
9-
* about the target function. All the current capabilities have to do
10-
* with planning queries that use the target function, though it is
11-
* possible that future extensions will add functionality to be invoked
12-
* by the parser or executor.
9+
* about the target function. The name is now something of a misnomer,
10+
* since some of the call sites are in the executor not the planner,
11+
* but "function support function" would be a confusing name so we
12+
* stick with "planner support function".
1313
*
1414
* A support function must have the SQL signature
1515
* supportfn(internal) returns internal
@@ -343,4 +343,51 @@ typedef struct SupportRequestOptimizeWindowClause
343343
* optimizations are possible. */
344344
} SupportRequestOptimizeWindowClause;
345345

346+
/*
347+
* The ModifyInPlace request allows the support function to detect whether
348+
* a call to its target function can be allowed to modify a read/write
349+
* expanded object in-place. The context is that we are considering a
350+
* PL/pgSQL (or similar PL) assignment of the form "x := f(x, ...)" where
351+
* the variable x is of a type that can be represented as an expanded object
352+
* (see utils/expandeddatum.h). If f() can usefully optimize by modifying
353+
* the passed-in object in-place, then this request can be implemented to
354+
* instruct PL/pgSQL to pass a read-write expanded pointer to the variable's
355+
* value. (Note that there is no guarantee that later calls to f() will
356+
* actually do so. If f() receives a read-only pointer, or a pointer to a
357+
* non-expanded object, it must follow the usual convention of not modifying
358+
* the pointed-to object.) There are two requirements that must be met
359+
* to make this safe:
360+
* 1. f() must guarantee that it will not have modified the object if it
361+
* fails. Otherwise the variable's value might change unexpectedly.
362+
* 2. If the other arguments to f() ("..." in the above example) contain
363+
* references to x, f() must be able to cope with that; or if that's not
364+
* safe, the support function must scan the other arguments to verify that
365+
* there are no other references to x. An example of the concern here is
366+
* that in "arr := array_append(arr, arr[1])", if the array element type
367+
* is pass-by-reference then array_append would receive a second argument
368+
* that points into the array object it intends to modify. array_append is
369+
* coded to make that safe, but other functions might not be able to cope.
370+
*
371+
* "args" is a node tree list representing the function's arguments.
372+
* One or more nodes within the node tree will be PARAM_EXTERN Params
373+
* with ID "paramid", which represent the assignment target variable.
374+
* (Note that such references are not necessarily at top level in the list,
375+
* for example we might have "x := f(x, g(x))". Generally it's only safe
376+
* to optimize a reference that is at top level, else we're making promises
377+
* about the behavior of g() as well as f().)
378+
*
379+
* If modify-in-place is safe, the support function should return the
380+
* address of the Param node that is to return a read-write pointer.
381+
* (At most one of the references is allowed to do so.) Otherwise,
382+
* return NULL.
383+
*/
384+
typedef struct SupportRequestModifyInPlace
385+
{
386+
NodeTag type;
387+
388+
Oid funcid; /* PG_PROC OID of the target function */
389+
List *args; /* Arguments to the function */
390+
int paramid; /* ID of Param(s) representing variable */
391+
} SupportRequestModifyInPlace;
392+
346393
#endif /* SUPPORTNODES_H */

src/pl/plpgsql/src/expected/plpgsql_array.out

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ begin
5757
-- test scenarios for optimization of updates of R/W expanded objects
5858
a := array_append(a, 42); -- optimizable using "transfer" method
5959
a := a || a[3]; -- optimizable using "inplace" method
60+
a := a[1] || a; -- ditto, but let's test array_prepend
6061
a := a || a; -- not optimizable
6162
raise notice 'a = %', a;
6263
end$$;
63-
NOTICE: a = {1,2,3,42,3,1,2,3,42,3}
64+
NOTICE: a = {1,1,2,3,42,3,1,1,2,3,42,3}
6465
create temp table onecol as select array[1,2] as f1;
6566
do $$ declare a int[];
6667
begin a := f1 from onecol; raise notice 'a = %', a; end$$;

src/pl/plpgsql/src/pl_exec.c

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "mb/stringinfo_mb.h"
3030
#include "miscadmin.h"
3131
#include "nodes/nodeFuncs.h"
32+
#include "nodes/supportnodes.h"
3233
#include "optimizer/optimizer.h"
3334
#include "parser/parse_coerce.h"
3435
#include "parser/parse_type.h"
@@ -8411,7 +8412,7 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid)
84118412
Expr *sexpr = expr->expr_simple_expr;
84128413
Oid funcid;
84138414
List *fargs;
8414-
ListCell *lc;
8415+
Oid prosupport;
84158416

84168417
/* Assume unsafe */
84178418
expr->expr_rwopt = PLPGSQL_RWOPT_NOPE;
@@ -8480,64 +8481,51 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid)
84808481
{
84818482
SubscriptingRef *sbsref = (SubscriptingRef *) sexpr;
84828483

8483-
/* We only trust standard varlena arrays to be safe */
8484-
/* TODO: install some extensibility here */
8485-
if (get_typsubscript(sbsref->refcontainertype, NULL) !=
8486-
F_ARRAY_SUBSCRIPT_HANDLER)
8487-
return;
8488-
8489-
/* We can optimize the refexpr if it's the target, otherwise not */
8490-
if (sbsref->refexpr && IsA(sbsref->refexpr, Param))
8491-
{
8492-
Param *param = (Param *) sbsref->refexpr;
8484+
funcid = get_typsubscript(sbsref->refcontainertype, NULL);
84938485

8494-
if (param->paramkind == PARAM_EXTERN &&
8495-
param->paramid == paramid)
8496-
{
8497-
/* Found the Param we want to pass as read/write */
8498-
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
8499-
expr->expr_rw_param = param;
8500-
return;
8501-
}
8502-
}
8503-
8504-
return;
8486+
/*
8487+
* We assume that only the refexpr and refassgnexpr (if any) are
8488+
* relevant to the support function's decision. If that turns out to
8489+
* be a bad idea, we could incorporate the subscript expressions into
8490+
* the fargs list somehow.
8491+
*/
8492+
fargs = list_make2(sbsref->refexpr, sbsref->refassgnexpr);
85058493
}
85068494
else
85078495
return;
85088496

85098497
/*
8510-
* The top-level function must be one that we trust to be "safe".
8511-
* Currently we hard-wire the list, but it would be very desirable to
8512-
* allow extensions to mark their functions as safe ...
8498+
* The top-level function must be one that can handle in-place update
8499+
* safely. We allow functions to declare their ability to do that via a
8500+
* support function request.
85138501
*/
8514-
if (!(funcid == F_ARRAY_APPEND ||
8515-
funcid == F_ARRAY_PREPEND))
8516-
return;
8517-
8518-
/*
8519-
* The target variable (in the form of a Param) must appear as a direct
8520-
* argument of the top-level function. References further down in the
8521-
* tree can't be optimized; but on the other hand, they don't invalidate
8522-
* optimizing the top-level call, since that will be executed last.
8523-
*/
8524-
foreach(lc, fargs)
8502+
prosupport = get_func_support(funcid);
8503+
if (OidIsValid(prosupport))
85258504
{
8526-
Node *arg = (Node *) lfirst(lc);
8505+
SupportRequestModifyInPlace req;
8506+
Param *param;
85278507

8528-
if (arg && IsA(arg, Param))
8529-
{
8530-
Param *param = (Param *) arg;
8508+
req.type = T_SupportRequestModifyInPlace;
8509+
req.funcid = funcid;
8510+
req.args = fargs;
8511+
req.paramid = paramid;
85318512

8532-
if (param->paramkind == PARAM_EXTERN &&
8533-
param->paramid == paramid)
8534-
{
8535-
/* Found the Param we want to pass as read/write */
8536-
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
8537-
expr->expr_rw_param = param;
8538-
return;
8539-
}
8540-
}
8513+
param = (Param *)
8514+
DatumGetPointer(OidFunctionCall1(prosupport,
8515+
PointerGetDatum(&req)));
8516+
8517+
if (param == NULL)
8518+
return; /* support function fails */
8519+
8520+
/* Verify support function followed the API */
8521+
Assert(IsA(param, Param));
8522+
Assert(param->paramkind == PARAM_EXTERN);
8523+
Assert(param->paramid == paramid);
8524+
8525+
/* Found the Param we want to pass as read/write */
8526+
expr->expr_rwopt = PLPGSQL_RWOPT_INPLACE;
8527+
expr->expr_rw_param = param;
8528+
return;
85418529
}
85428530
}
85438531

src/pl/plpgsql/src/sql/plpgsql_array.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ begin
5353
-- test scenarios for optimization of updates of R/W expanded objects
5454
a := array_append(a, 42); -- optimizable using "transfer" method
5555
a := a || a[3]; -- optimizable using "inplace" method
56+
a := a[1] || a; -- ditto, but let's test array_prepend
5657
a := a || a; -- not optimizable
5758
raise notice 'a = %', a;
5859
end$$;

src/tools/pgindent/typedefs.list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2804,6 +2804,7 @@ SubscriptionRelState
28042804
SummarizerReadLocalXLogPrivate
28052805
SupportRequestCost
28062806
SupportRequestIndexCondition
2807+
SupportRequestModifyInPlace
28072808
SupportRequestOptimizeWindowClause
28082809
SupportRequestRows
28092810
SupportRequestSelectivity

0 commit comments

Comments
 (0)