diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/backend/utils/adt/array_userfuncs.c | 61 | ||||
-rw-r--r-- | src/backend/utils/adt/arraysubs.c | 34 | ||||
-rw-r--r-- | src/include/catalog/catversion.h | 2 | ||||
-rw-r--r-- | src/include/catalog/pg_proc.dat | 20 | ||||
-rw-r--r-- | src/include/nodes/supportnodes.h | 55 | ||||
-rw-r--r-- | src/pl/plpgsql/src/expected/plpgsql_array.out | 3 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 86 | ||||
-rw-r--r-- | src/pl/plpgsql/src/sql/plpgsql_array.sql | 1 | ||||
-rw-r--r-- | src/tools/pgindent/typedefs.list | 1 |
9 files changed, 203 insertions, 60 deletions
diff --git a/src/backend/utils/adt/array_userfuncs.c b/src/backend/utils/adt/array_userfuncs.c index 0b02fe37445..2aae2f8ed93 100644 --- a/src/backend/utils/adt/array_userfuncs.c +++ b/src/backend/utils/adt/array_userfuncs.c @@ -16,6 +16,7 @@ #include "common/int.h" #include "common/pg_prng.h" #include "libpq/pqformat.h" +#include "nodes/supportnodes.h" #include "port/pg_bitutils.h" #include "utils/array.h" #include "utils/builtins.h" @@ -167,6 +168,36 @@ array_append(PG_FUNCTION_ARGS) PG_RETURN_DATUM(result); } +/* + * array_append_support() + * + * Planner support function for array_append() + */ +Datum +array_append_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestModifyInPlace)) + { + /* + * We can optimize in-place appends if the function's array argument + * is the array being assigned to. We don't need to worry about array + * references within the other argument. + */ + SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq; + Param *arg = (Param *) linitial(req->args); + + if (arg && IsA(arg, Param) && + arg->paramkind == PARAM_EXTERN && + arg->paramid == req->paramid) + ret = (Node *) arg; + } + + PG_RETURN_POINTER(ret); +} + /*----------------------------------------------------------------------------- * array_prepend : * push an element onto the front of a one-dimensional array @@ -230,6 +261,36 @@ array_prepend(PG_FUNCTION_ARGS) PG_RETURN_DATUM(result); } +/* + * array_prepend_support() + * + * Planner support function for array_prepend() + */ +Datum +array_prepend_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestModifyInPlace)) + { + /* + * We can optimize in-place prepends if the function's array argument + * is the array being assigned to. We don't need to worry about array + * references within the other argument. + */ + SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq; + Param *arg = (Param *) lsecond(req->args); + + if (arg && IsA(arg, Param) && + arg->paramkind == PARAM_EXTERN && + arg->paramid == req->paramid) + ret = (Node *) arg; + } + + PG_RETURN_POINTER(ret); +} + /*----------------------------------------------------------------------------- * array_cat : * concatenate two nD arrays to form an nD array, or diff --git a/src/backend/utils/adt/arraysubs.c b/src/backend/utils/adt/arraysubs.c index 562179b3799..2940fb8e8d7 100644 --- a/src/backend/utils/adt/arraysubs.c +++ b/src/backend/utils/adt/arraysubs.c @@ -18,6 +18,7 @@ #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/subscripting.h" +#include "nodes/supportnodes.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "utils/array.h" @@ -575,3 +576,36 @@ raw_array_subscript_handler(PG_FUNCTION_ARGS) PG_RETURN_POINTER(&sbsroutines); } + +/* + * array_subscript_handler_support() + * + * Planner support function for array_subscript_handler() + */ +Datum +array_subscript_handler_support(PG_FUNCTION_ARGS) +{ + Node *rawreq = (Node *) PG_GETARG_POINTER(0); + Node *ret = NULL; + + if (IsA(rawreq, SupportRequestModifyInPlace)) + { + /* + * We can optimize in-place subscripted assignment if the refexpr is + * the array being assigned to. We don't need to worry about array + * references within the refassgnexpr or the subscripts; however, if + * there's no refassgnexpr then it's a fetch which there's no need to + * optimize. + */ + SupportRequestModifyInPlace *req = (SupportRequestModifyInPlace *) rawreq; + Param *refexpr = (Param *) linitial(req->args); + + if (refexpr && IsA(refexpr, Param) && + refexpr->paramkind == PARAM_EXTERN && + refexpr->paramid == req->paramid && + lsecond(req->args) != NULL) + ret = (Node *) refexpr; + } + + PG_RETURN_POINTER(ret); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index eddbd298091..f7226b8e439 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202502071 +#define CATALOG_VERSION_NO 202502111 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 5b8c2ad2a54..9e803d610d7 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -1598,14 +1598,20 @@ proname => 'cardinality', prorettype => 'int4', proargtypes => 'anyarray', prosrc => 'array_cardinality' }, { oid => '378', descr => 'append element onto end of array', - proname => 'array_append', proisstrict => 'f', - prorettype => 'anycompatiblearray', + proname => 'array_append', prosupport => 'array_append_support', + proisstrict => 'f', prorettype => 'anycompatiblearray', proargtypes => 'anycompatiblearray anycompatible', prosrc => 'array_append' }, +{ oid => '8680', descr => 'planner support for array_append', + proname => 'array_append_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'array_append_support' }, { oid => '379', descr => 'prepend element onto front of array', - proname => 'array_prepend', proisstrict => 'f', - prorettype => 'anycompatiblearray', + proname => 'array_prepend', prosupport => 'array_prepend_support', + proisstrict => 'f', prorettype => 'anycompatiblearray', proargtypes => 'anycompatible anycompatiblearray', prosrc => 'array_prepend' }, +{ oid => '8681', descr => 'planner support for array_prepend', + proname => 'array_prepend_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'array_prepend_support' }, { oid => '383', proname => 'array_cat', proisstrict => 'f', prorettype => 'anycompatiblearray', @@ -12207,8 +12213,12 @@ # subscripting support for built-in types { oid => '6179', descr => 'standard array subscripting support', - proname => 'array_subscript_handler', prorettype => 'internal', + proname => 'array_subscript_handler', + prosupport => 'array_subscript_handler_support', prorettype => 'internal', proargtypes => 'internal', prosrc => 'array_subscript_handler' }, +{ oid => '8682', descr => 'planner support for array_subscript_handler', + proname => 'array_subscript_handler_support', prorettype => 'internal', + proargtypes => 'internal', prosrc => 'array_subscript_handler_support' }, { oid => '6180', descr => 'raw array subscripting support', proname => 'raw_array_subscript_handler', prorettype => 'internal', proargtypes => 'internal', prosrc => 'raw_array_subscript_handler' }, diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h index ad5d43a2a70..9c047cc401b 100644 --- a/src/include/nodes/supportnodes.h +++ b/src/include/nodes/supportnodes.h @@ -6,10 +6,10 @@ * This file defines the API for "planner support functions", which * are SQL functions (normally written in C) that can be attached to * another "target" function to give the system additional knowledge - * about the target function. All the current capabilities have to do - * with planning queries that use the target function, though it is - * possible that future extensions will add functionality to be invoked - * by the parser or executor. + * about the target function. The name is now something of a misnomer, + * since some of the call sites are in the executor not the planner, + * but "function support function" would be a confusing name so we + * stick with "planner support function". * * A support function must have the SQL signature * supportfn(internal) returns internal @@ -343,4 +343,51 @@ typedef struct SupportRequestOptimizeWindowClause * optimizations are possible. */ } SupportRequestOptimizeWindowClause; +/* + * The ModifyInPlace request allows the support function to detect whether + * a call to its target function can be allowed to modify a read/write + * expanded object in-place. The context is that we are considering a + * PL/pgSQL (or similar PL) assignment of the form "x := f(x, ...)" where + * the variable x is of a type that can be represented as an expanded object + * (see utils/expandeddatum.h). If f() can usefully optimize by modifying + * the passed-in object in-place, then this request can be implemented to + * instruct PL/pgSQL to pass a read-write expanded pointer to the variable's + * value. (Note that there is no guarantee that later calls to f() will + * actually do so. If f() receives a read-only pointer, or a pointer to a + * non-expanded object, it must follow the usual convention of not modifying + * the pointed-to object.) There are two requirements that must be met + * to make this safe: + * 1. f() must guarantee that it will not have modified the object if it + * fails. Otherwise the variable's value might change unexpectedly. + * 2. If the other arguments to f() ("..." in the above example) contain + * references to x, f() must be able to cope with that; or if that's not + * safe, the support function must scan the other arguments to verify that + * there are no other references to x. An example of the concern here is + * that in "arr := array_append(arr, arr[1])", if the array element type + * is pass-by-reference then array_append would receive a second argument + * that points into the array object it intends to modify. array_append is + * coded to make that safe, but other functions might not be able to cope. + * + * "args" is a node tree list representing the function's arguments. + * One or more nodes within the node tree will be PARAM_EXTERN Params + * with ID "paramid", which represent the assignment target variable. + * (Note that such references are not necessarily at top level in the list, + * for example we might have "x := f(x, g(x))". Generally it's only safe + * to optimize a reference that is at top level, else we're making promises + * about the behavior of g() as well as f().) + * + * If modify-in-place is safe, the support function should return the + * address of the Param node that is to return a read-write pointer. + * (At most one of the references is allowed to do so.) Otherwise, + * return NULL. + */ +typedef struct SupportRequestModifyInPlace +{ + NodeTag type; + + Oid funcid; /* PG_PROC OID of the target function */ + List *args; /* Arguments to the function */ + int paramid; /* ID of Param(s) representing variable */ +} SupportRequestModifyInPlace; + #endif /* SUPPORTNODES_H */ diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out index e5db6d60876..4c6b3ce998a 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_array.out +++ b/src/pl/plpgsql/src/expected/plpgsql_array.out @@ -57,10 +57,11 @@ 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[1] || a; -- ditto, but let's test array_prepend a := a || a; -- not optimizable raise notice 'a = %', a; end$$; -NOTICE: a = {1,2,3,42,3,1,2,3,42,3} +NOTICE: a = {1,1,2,3,42,3,1,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 28b6c85d8d2..d4377ceecbf 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -29,6 +29,7 @@ #include "mb/stringinfo_mb.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "nodes/supportnodes.h" #include "optimizer/optimizer.h" #include "parser/parse_coerce.h" #include "parser/parse_type.h" @@ -8411,7 +8412,7 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid) Expr *sexpr = expr->expr_simple_expr; Oid funcid; List *fargs; - ListCell *lc; + Oid prosupport; /* Assume unsafe */ expr->expr_rwopt = PLPGSQL_RWOPT_NOPE; @@ -8480,64 +8481,51 @@ exec_check_rw_parameter(PLpgSQL_expr *expr, int paramid) { 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; - - /* We can optimize the refexpr if it's the target, otherwise not */ - if (sbsref->refexpr && IsA(sbsref->refexpr, Param)) - { - Param *param = (Param *) sbsref->refexpr; + funcid = get_typsubscript(sbsref->refcontainertype, NULL); - if (param->paramkind == PARAM_EXTERN && - 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; - } - } - - return; + /* + * We assume that only the refexpr and refassgnexpr (if any) are + * relevant to the support function's decision. If that turns out to + * be a bad idea, we could incorporate the subscript expressions into + * the fargs list somehow. + */ + fargs = list_make2(sbsref->refexpr, sbsref->refassgnexpr); } else return; /* - * The top-level function must be one that we trust to be "safe". - * Currently we hard-wire the list, but it would be very desirable to - * allow extensions to mark their functions as safe ... + * The top-level function must be one that can handle in-place update + * safely. We allow functions to declare their ability to do that via a + * support function request. */ - if (!(funcid == F_ARRAY_APPEND || - funcid == F_ARRAY_PREPEND)) - return; - - /* - * The target variable (in the form of a Param) must appear as a direct - * argument of the top-level function. References further down in the - * tree can't be optimized; but on the other hand, they don't invalidate - * optimizing the top-level call, since that will be executed last. - */ - foreach(lc, fargs) + prosupport = get_func_support(funcid); + if (OidIsValid(prosupport)) { - Node *arg = (Node *) lfirst(lc); + SupportRequestModifyInPlace req; + Param *param; - if (arg && IsA(arg, Param)) - { - Param *param = (Param *) arg; + req.type = T_SupportRequestModifyInPlace; + req.funcid = funcid; + req.args = fargs; + req.paramid = paramid; - if (param->paramkind == PARAM_EXTERN && - 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; - } - } + param = (Param *) + DatumGetPointer(OidFunctionCall1(prosupport, + PointerGetDatum(&req))); + + if (param == NULL) + return; /* support function fails */ + + /* Verify support function followed the API */ + Assert(IsA(param, Param)); + Assert(param->paramkind == PARAM_EXTERN); + Assert(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; } } diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql index 4a346203dc2..da984a99414 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_array.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql @@ -53,6 +53,7 @@ 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[1] || a; -- ditto, but let's test array_prepend a := a || a; -- not optimizable raise notice 'a = %', a; end$$; diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 110aa8ab5a2..b6c170ac249 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2804,6 +2804,7 @@ SubscriptionRelState SummarizerReadLocalXLogPrivate SupportRequestCost SupportRequestIndexCondition +SupportRequestModifyInPlace SupportRequestOptimizeWindowClause SupportRequestRows SupportRequestSelectivity |