The attached patch contains a helper now named MakeFuncResultTuplestore() which aims to eliminate a few lines of redundant code that all FUNCAPI functions making tuplestores repeated.
While investigating all of these call sites and making the changes suggested by Álvaro, I noticed that the functions making tuplestores broke out into seven groups. For some of them, using my helper changes what memory context the tuple descriptor is made in. Explanation of how/why memory context tuple descriptor is in has changed: An earlier version of this patch changed into the per query memory context inside of the helper function. This version does not do so but instead asserts that the caller has changed into the appropriate memory context. For callers which previously did not call get_call_result_type() and instead used a function like CreateTupleDescCopy() to create the tuple descriptor which was used for the tuples in the tuplestore, most of them made this copy in the per query memory context. For these callers it seemed error-prone to switch to the per query memory context to create the copied tuple descriptor and then switch back before calling the helper function. Instead, the helper function will assert that the caller has already switched into the per query memory context so that the tuplestore is made in the per query context. This means that for the group of functions which, prior to this patch, called get_call_result_type() in a shorter-lived memory context but now rely on the helper function to call get_call_result_type(), the tuple descriptor will now be in the per query memory context. I think that this makes sense since most of these are set as the ResultSetInfo->setDesc (the tuple descriptor for returned tuples). Explanation of the different groups of functions using the MakeFuncResultTuplestore() helper: The first group are functions which called get_call_result_type(), allocating memory for the tuple descriptor in the short-lived memory context, then switched into the per query memory context before creating the tuplestore, then switched back to the short-lived memory context directly after making the tuplestore. Now that they use the helper function and switch into the per query memory context before calling it, the tuple descriptor is allocated in the per query memory context. The first group: pg_stop_backup_v2() pg_event_trigger_dropped_objects() pg_event_trigger_ddl_commands() pg_available_extensions() pg_available_extension_versions() pg_extension_update_paths() pg_hba_file_rules() pg_stat_get_subscription() pg_logical_slot_get_changes_guts() pg_show_replication_origin_status() pg_get_replication_slots() pg_stat_get_wal_senders() pg_get_shmem_allocations() pg_timezone_names() pg_ls_dir_files() pg_get_backend_memory_contexts() pg_stat_get_progress_info() pg_stat_get_activity() pg_stat_get_slru() The second group are functions which, instead of calling get_call_result_type(), call CreateTemplateTupleDesc() and TupleDescInitEntry() to allocate and initialize the tuple descriptor in the per query memory context. Their memory usage is unchanged by this patch. The second group: pg_prepared_statement() pg_ls_dir() pg_tablespace_databases() show_all_file_settings() pg_cursor() The third and fourth groups are functions that use CreateTupleDescCopy() to make a tuple descriptor copy from ResultSetInfo->expectedDesc. These functions made a copy of ResultSetInfo->expectedDesc but did not check if it was present before doing so (and do not seem to call a function which would call internal_get_result_type() -- which does this check), so I have added that check. Their memory usage is unchanged by this patch. The third group: text_to_table() The fourth group differs from the third in that the values for the tuples are actually created in the per query memory context -- seemingly unnecessarily since tuplestore_putvalues() will eventually end up copying everything when it is forming tuples. This behavior is not altered at all by the MakeFuncResultTuplestore() helper function patch, however I wanted to mention that I noticed that perhaps more was being allocated in the per query memory context than is necessary. The fourth group: pg_options_to_table() pg_config() The fifth group is a function that uses CreateTupleDescCoy() to make a copy of a tuple descriptor (NOT ResultSetInfo->expectedDesc) in the function info memory context (fcinfo->flinfo->fn_mcxt) so that it can be accessed later. Their memory usage is unchanged by this patch. The fifth group: populate_recordset_worker() The sixth group of functions use CreateTupleDescCopy() to make a copy of the tuple descriptor that they set up using get_call_result_type(). They called get_call_result_type() in a short-lived memory context, then switched to the per query memory context and made a copy of it right away. It is unclear why they couldn't just call get_call_result_type() in the per query context and not make a copy. Perhaps they needed some specific behavior of the copy (like that it doesn't copy constraints and defaults?). With the attached patch, the initial tuple descriptor is now made in the per query memory context (as well as the copy). I wonder if the initial tuple descriptor being made in the per query memory context negated the need for a copy, however I left the copy intact since I wasn't sure. These functions also checked for the presence of ResultSetInfo->expectedDesc which is not applicable for all callers of MakeFuncResultTuplestore(), so I separated it into a separate check. get_call_result_type() will call internal_get_result_type() which will check for expectedDesc if the result type is TYPEFUNC_RECORD, so I'm not sure if the expectedDesc check is required or not in the body of these functions. The sixth group: each_worker() each_worker_jsonb() The seventh group of functions use CreateTupleDescCopy() to make a copy of ResultSetInfo->expectedDesc in the per query memory context. This patch does not change any of that. The only difference is that now the check for expectedDesc is separate. Their memory usage is unchanged by this patch. The seventh group: elements_worker_jsonb() elements_worker() Note that for all of the functions in which I added or changed the check for ResultSetInfo->expectedDesc, it is a little bit different because I've changed the error code. I've used error code: ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED where they previously would have been part of an ereport with error code ERRCODE_FEATURE_NOT_SUPPORTED. I'm not sure if the new one is correct. In fact, I'm not sure if an error related to expectedDesc being set should be user-facing at all (looking at how expectedDesc is used and set, I wasn't sure). Perhaps it should be an elog() or an assert? Feedback is addressed inline below. On Tue, Nov 2, 2021 at 2:17 PM Alvaro Herrera <alvhe...@alvh.no-ip.org> wrote: > > On 2021-Nov-02, Melanie Plageman wrote: > > > Attached is a patch to create a helper function which creates a > > tuplestore to be used by FUNCAPI-callable functions. > > Looks good, and given the amount of code being removed, it seems a > no-brainer that some helper(s) is/are needed. > > I think the name is a bit too generic. How about > MakeFuncResultTuplestore()? I've done this rename. > I think the function should not modify rsinfo -- having that as > (undocumented) side effect is weird. I would suggest to just return the > tuplestore, and have the caller stash it in rsinfo->setResult and modify > the other struct members alongside that. It's a couple more lines per > caller, but the code is clearer that way. I've changed the helper not to modify rsinfo. > > There are a few places with very similar code to the new helper > > (MakeTuplestore()) but which, for example, don't expect the return type > > to be a row (pg_ls_dir(), pg_tablespace_databases(), text_to_table()). I > > wasn't sure if it was worth modifying it for their benefit. > > Is this just because of the tupdesc? If that's the case, then maybe the > tupdesc can be optionally passed in by caller, and when it's not, the > helper does get_call_result_type() and the tupdesc is used as output > argument. I've parameterized the helper in this way and now am able to use it for all FUNCAPI-callable functions making tuplestores except fmgr_sql (for SQL functions) in functions.c because it didn't really seem worth it given the way the code is written. > > All callers of MakeTuplestore() pass work_mem as the third parameter, so > > I could just omit it and hard-code it in, but I wasn't sure that felt > > right in a utility/helper function. > > No opinion. I would have used the global in the function instead of > passing it in. I've changed this to use the global. > > I inlined deflist_to_tuplestore() in foreign.c since it was billed as a > > helper function but wasn't used elsewhere and it made it harder to use > > MakeTuplestore() in this location. > > This is a bit strange. Does it work to pass rsinfo->expectedDesc? This is addressed by changing MakeFuncResultTuplestore() call get_call_result_type() optionally and having pg_options_to_table() call it with NULL as tuple descriptor. Now the behavior is the same -- it makes a copy of expectedDesc in the per query context and sets that as setDesc in the ReturnSetInfo. - Melanie
From f4b8d63e5d49e7ce34b5722c5421ed1ed19daba1 Mon Sep 17 00:00:00 2001 From: Melanie Plageman <melanieplage...@gmail.com> Date: Tue, 2 Nov 2021 12:20:55 -0400 Subject: [PATCH v2] Add helper to make tuplestore And use it to refactor out existing duplicate code. --- src/backend/access/transam/xlogfuncs.c | 16 +--- src/backend/commands/event_trigger.c | 32 +------- src/backend/commands/extension.c | 48 +---------- src/backend/commands/prepare.c | 15 +--- src/backend/foreign/foreign.c | 51 +++++------- src/backend/libpq/hba.c | 19 +---- src/backend/replication/logical/launcher.c | 16 +--- .../replication/logical/logicalfuncs.c | 18 +--- src/backend/replication/logical/origin.c | 19 +---- src/backend/replication/slotfuncs.c | 16 +--- src/backend/replication/walsender.c | 16 +--- src/backend/storage/ipc/shmem.c | 16 +--- src/backend/utils/adt/datetime.c | 17 +--- src/backend/utils/adt/genfile.c | 31 +------ src/backend/utils/adt/jsonfuncs.c | 82 ++++++------------- src/backend/utils/adt/mcxtfuncs.c | 16 +--- src/backend/utils/adt/misc.c | 14 +--- src/backend/utils/adt/pgstatfuncs.c | 48 +---------- src/backend/utils/adt/varlena.c | 15 ++-- src/backend/utils/fmgr/funcapi.c | 32 ++++++++ src/backend/utils/misc/guc.c | 12 +-- src/backend/utils/misc/pg_config.c | 11 ++- src/backend/utils/mmgr/portalmem.c | 15 +--- src/include/funcapi.h | 1 + 24 files changed, 118 insertions(+), 458 deletions(-) diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index dd9a45c186..59d77adf6e 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -178,24 +178,10 @@ pg_stop_backup_v2(PG_FUNCTION_ARGS) XLogRecPtr stoppoint; SessionBackupState status = get_backup_status(); - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index df264329d8..c20473cbb1 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -1306,25 +1306,11 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS) errmsg("%s can only be called in a sql_drop event trigger function", "pg_event_trigger_dropped_objects()"))); - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -1864,25 +1850,11 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) errmsg("%s can only be called in an event trigger function", "pg_event_trigger_ddl_commands()"))); - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index eaa76af47b..fe9bf52ca8 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -1929,25 +1929,11 @@ pg_available_extensions(PG_FUNCTION_ARGS) DIR *dir; struct dirent *de; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -2037,25 +2023,11 @@ pg_available_extension_versions(PG_FUNCTION_ARGS) DIR *dir; struct dirent *de; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -2322,25 +2294,11 @@ pg_extension_update_paths(PG_FUNCTION_ARGS) /* Check extension name validity before any filesystem access */ check_valid_extension_name(NameStr(*extname)); - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 5e03c7c5aa..32fc055792 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -22,6 +22,7 @@ #include "catalog/pg_type.h" #include "commands/createas.h" #include "commands/prepare.h" +#include "funcapi.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "parser/analyze.h" @@ -706,16 +707,6 @@ pg_prepared_statement(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* need to build tuplestore in query context */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); @@ -744,9 +735,7 @@ pg_prepared_statement(PG_FUNCTION_ARGS) * We put all the tuples into a tuplestore in one scan of the hashtable. * This avoids any issue of the hashtable possibly changing between calls. */ - tupstore = - tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, - false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); /* generate junk in short-term context */ MemoryContextSwitchTo(oldcontext); diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c index e07cc57431..9bfc5f57f9 100644 --- a/src/backend/foreign/foreign.c +++ b/src/backend/foreign/foreign.c @@ -20,6 +20,7 @@ #include "catalog/pg_user_mapping.h" #include "foreign/fdwapi.h" #include "foreign/foreign.h" +#include "funcapi.h" #include "lib/stringinfo.h" #include "miscadmin.h" #include "utils/builtins.h" @@ -499,30 +500,34 @@ IsImportableForeignTable(const char *tablename, /* - * deflist_to_tuplestore - Helper function to convert DefElem list to - * tuplestore usable in SRF. + * Convert options array to name/value table. Useful for information + * schema and pg_dump. */ -static void -deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options) +Datum +pg_options_to_table(PG_FUNCTION_ARGS) { + Datum array = PG_GETARG_DATUM(0); ListCell *cell; + List *options; + + ReturnSetInfo *rsinfo; TupleDesc tupdesc; Tuplestorestate *tupstore; - Datum values[2]; - bool nulls[2]; + MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize) || - rsinfo->expectedDesc == NULL) + Datum values[2]; + bool nulls[2]; + + options = untransformRelOptions(array); + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + if (!rsinfo->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); @@ -531,7 +536,7 @@ deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options) * Now prepare the result set. */ tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -559,20 +564,6 @@ deflist_to_tuplestore(ReturnSetInfo *rsinfo, List *options) tuplestore_donestoring(tupstore); MemoryContextSwitchTo(oldcontext); -} - - -/* - * Convert options array to name/value table. Useful for information - * schema and pg_dump. - */ -Datum -pg_options_to_table(PG_FUNCTION_ARGS) -{ - Datum array = PG_GETARG_DATUM(0); - - deflist_to_tuplestore((ReturnSetInfo *) fcinfo->resultinfo, - untransformRelOptions(array)); return (Datum) 0; } diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 3be8778d21..96dde746aa 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -2710,29 +2710,12 @@ pg_hba_file_rules(PG_FUNCTION_ARGS) * up our current position in the parsed list every time. */ rsi = (ReturnSetInfo *) fcinfo->resultinfo; - - /* Check to see if caller supports us returning a tuplestore */ - if (rsi == NULL || !IsA(rsi, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsi->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - rsi->returnMode = SFRM_Materialize; - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Build tuplestore to hold the result rows */ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); - tuple_store = - tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, - false, work_mem); + tuple_store = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsi->setDesc = tupdesc; rsi->setResult = tuple_store; diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index 3fb4caa803..f1d2665dce 100644 --- a/src/backend/replication/logical/launcher.c +++ b/src/backend/replication/logical/launcher.c @@ -935,24 +935,10 @@ pg_stat_get_subscription(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c index 2609a0a710..28f99d711d 100644 --- a/src/backend/replication/logical/logicalfuncs.c +++ b/src/backend/replication/logical/logicalfuncs.c @@ -141,25 +141,11 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin errmsg("options array must not be null"))); arr = PG_GETARG_ARRAYTYPE_P(3); - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* state to write output to */ p = palloc0(sizeof(DecodingOutputState)); p->binary_output = binary; - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &p->tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); @@ -202,7 +188,7 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin } } - p->tupstore = tuplestore_begin_heap(true, false, work_mem); + p->tupstore = MakeFuncResultTuplestore(fcinfo, &p->tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = p->tupstore; rsinfo->setDesc = p->tupdesc; @@ -295,8 +281,6 @@ pg_logical_slot_get_changes_guts(FunctionCallInfo fcinfo, bool confirm, bool bin CHECK_FOR_INTERRUPTS(); } - tuplestore_donestoring(tupstore); - /* * Logical decoding could have clobbered CurrentResourceOwner during * transaction management, so restore the executor's value. (This is diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c index 65dcd033fd..a88cdc2f17 100644 --- a/src/backend/replication/logical/origin.c +++ b/src/backend/replication/logical/origin.c @@ -1491,30 +1491,19 @@ pg_show_replication_origin_status(PG_FUNCTION_ARGS) /* we want to return 0 rows if slot is set to zero */ replorigin_check_prerequisites(false, true); - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - if (tupdesc->natts != REPLICATION_ORIGIN_PROGRESS_COLS) - elog(ERROR, "wrong function definition"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); + rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; MemoryContextSwitchTo(oldcontext); + if (tupdesc->natts != REPLICATION_ORIGIN_PROGRESS_COLS) + elog(ERROR, "wrong function definition"); /* prevent slots from being concurrently dropped */ LWLockAcquire(ReplicationOriginLock, LW_SHARED); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index a80298ba53..86f0d4c75a 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -239,20 +239,6 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) XLogRecPtr currlsn; int slotno; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* * We don't require any special permission to see this function's data * because nothing should be sensitive. The most critical being the slot @@ -262,7 +248,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index fff7dfc640..b004207dde 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -3399,24 +3399,10 @@ pg_stat_get_wal_senders(PG_FUNCTION_ARGS) int num_standbys; int i; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 4425e99f17..ec4dd9caf8 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -547,24 +547,10 @@ pg_get_shmem_allocations(PG_FUNCTION_ARGS) Datum values[PG_GET_SHMEM_SIZES_COLS]; bool nulls[PG_GET_SHMEM_SIZES_COLS]; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c index cb3fa85892..83b6b2bb33 100644 --- a/src/backend/utils/adt/datetime.c +++ b/src/backend/utils/adt/datetime.c @@ -4786,7 +4786,6 @@ Datum pg_timezone_names(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - bool randomAccess; TupleDesc tupdesc; Tuplestorestate *tupstore; pg_tzenum *tzenum; @@ -4801,24 +4800,10 @@ pg_timezone_names(PG_FUNCTION_ARGS) struct pg_tm itm; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/adt/genfile.c b/src/backend/utils/adt/genfile.c index c436d9318b..b443870f92 100644 --- a/src/backend/utils/adt/genfile.c +++ b/src/backend/utils/adt/genfile.c @@ -482,7 +482,6 @@ pg_ls_dir(PG_FUNCTION_ARGS) char *location; bool missing_ok = false; bool include_dot_dirs = false; - bool randomAccess; TupleDesc tupdesc; Tuplestorestate *tupstore; DIR *dirdesc; @@ -500,24 +499,13 @@ pg_ls_dir(PG_FUNCTION_ARGS) include_dot_dirs = PG_GETARG_BOOL(2); } - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); tupdesc = CreateTemplateTupleDesc(1); TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_ls_dir", TEXTOID, -1, 0); - randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -576,31 +564,16 @@ static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *dir, bool missing_ok) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - bool randomAccess; TupleDesc tupdesc; Tuplestorestate *tupstore; DIR *dirdesc; struct dirent *de; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - - randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/adt/jsonfuncs.c b/src/backend/utils/adt/jsonfuncs.c index 6335845d08..dbd5cf55bd 100644 --- a/src/backend/utils/adt/jsonfuncs.c +++ b/src/backend/utils/adt/jsonfuncs.c @@ -1926,30 +1926,20 @@ each_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, bool as_text) funcname))); rsi = (ReturnSetInfo *) fcinfo->resultinfo; - - if (!rsi || !IsA(rsi, ReturnSetInfo) || - (rsi->allowedModes & SFRM_Materialize) == 0 || - rsi->expectedDesc == NULL) + if (!rsi->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that " - "cannot accept a set"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); rsi->returnMode = SFRM_Materialize; - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("function returning record called in context " - "that cannot accept type record"))); - old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + tuple_store = MakeFuncResultTuplestore(fcinfo, &tupdesc); + ret_tdesc = CreateTupleDescCopy(tupdesc); BlessTupleDesc(ret_tdesc); - tuple_store = - tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, - false, work_mem); MemoryContextSwitchTo(old_cxt); @@ -2038,27 +2028,21 @@ each_worker(FunctionCallInfo fcinfo, bool as_text) sem = palloc0(sizeof(JsonSemAction)); rsi = (ReturnSetInfo *) fcinfo->resultinfo; - - if (!rsi || !IsA(rsi, ReturnSetInfo) || - (rsi->allowedModes & SFRM_Materialize) == 0 || - rsi->expectedDesc == NULL) + if (!rsi->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that " - "cannot accept a set"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); rsi->returnMode = SFRM_Materialize; - (void) get_call_result_type(fcinfo, NULL, &tupdesc); - /* make these in a sufficiently long-lived memory context */ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); + state->tuple_store = MakeFuncResultTuplestore(fcinfo, &tupdesc); + state->ret_tdesc = CreateTupleDescCopy(tupdesc); BlessTupleDesc(state->ret_tdesc); - state->tuple_store = - tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, - false, work_mem); MemoryContextSwitchTo(old_cxt); @@ -2226,14 +2210,11 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, errmsg("cannot extract elements from an object"))); rsi = (ReturnSetInfo *) fcinfo->resultinfo; - - if (!rsi || !IsA(rsi, ReturnSetInfo) || - (rsi->allowedModes & SFRM_Materialize) == 0 || - rsi->expectedDesc == NULL) + if (!rsi->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that " - "cannot accept a set"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); rsi->returnMode = SFRM_Materialize; @@ -2244,9 +2225,7 @@ elements_worker_jsonb(FunctionCallInfo fcinfo, const char *funcname, ret_tdesc = CreateTupleDescCopy(tupdesc); BlessTupleDesc(ret_tdesc); - tuple_store = - tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, - false, work_mem); + tuple_store = MakeFuncResultTuplestore(fcinfo, NULL); MemoryContextSwitchTo(old_cxt); @@ -2335,14 +2314,12 @@ elements_worker(FunctionCallInfo fcinfo, const char *funcname, bool as_text) sem = palloc0(sizeof(JsonSemAction)); rsi = (ReturnSetInfo *) fcinfo->resultinfo; - - if (!rsi || !IsA(rsi, ReturnSetInfo) || - (rsi->allowedModes & SFRM_Materialize) == 0 || - rsi->expectedDesc == NULL) + if (!rsi->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that " - "cannot accept a set"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); + rsi->returnMode = SFRM_Materialize; @@ -2354,9 +2331,7 @@ elements_worker(FunctionCallInfo fcinfo, const char *funcname, bool as_text) state->ret_tdesc = CreateTupleDescCopy(tupdesc); BlessTupleDesc(state->ret_tdesc); - state->tuple_store = - tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random, - false, work_mem); + state->tuple_store = MakeFuncResultTuplestore(fcinfo, NULL); MemoryContextSwitchTo(old_cxt); @@ -3798,13 +3773,6 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, rsi = (ReturnSetInfo *) fcinfo->resultinfo; - if (!rsi || !IsA(rsi, ReturnSetInfo) || - (rsi->allowedModes & SFRM_Materialize) == 0) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that " - "cannot accept a set"))); - rsi->returnMode = SFRM_Materialize; /* @@ -3871,9 +3839,7 @@ populate_recordset_worker(FunctionCallInfo fcinfo, const char *funcname, /* make tuplestore in a sufficiently long-lived memory context */ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); - state->tuple_store = tuplestore_begin_heap(rsi->allowedModes & - SFRM_Materialize_Random, - false, work_mem); + state->tuple_store = MakeFuncResultTuplestore(fcinfo, NULL); MemoryContextSwitchTo(old_cxt); state->function_name = funcname; diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c index 6ddbf70b30..bc1ba622b0 100644 --- a/src/backend/utils/adt/mcxtfuncs.c +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -125,24 +125,10 @@ pg_get_backend_memory_contexts(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index 88faf4dfd7..8398657a93 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -203,7 +203,6 @@ pg_tablespace_databases(PG_FUNCTION_ARGS) { Oid tablespaceOid = PG_GETARG_OID(0); ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; - bool randomAccess; TupleDesc tupdesc; Tuplestorestate *tupstore; char *location; @@ -211,16 +210,6 @@ pg_tablespace_databases(PG_FUNCTION_ARGS) struct dirent *de; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* The tupdesc and tuplestore must be created in ecxt_per_query_memory */ oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); @@ -228,8 +217,7 @@ pg_tablespace_databases(PG_FUNCTION_ARGS) TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pg_tablespace_databases", OIDOID, -1, 0); - randomAccess = (rsinfo->allowedModes & SFRM_Materialize_Random) != 0; - tupstore = tuplestore_begin_heap(randomAccess, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index ff5aedc99c..1e8de981cd 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -467,20 +467,6 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - /* Translate command name into command type code. */ if (pg_strcasecmp(cmd, "VACUUM") == 0) cmdtype = PROGRESS_COMMAND_VACUUM; @@ -502,7 +488,7 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS) per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -577,24 +563,10 @@ pg_stat_get_activity(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; @@ -1879,24 +1851,10 @@ pg_stat_get_slru(PG_FUNCTION_ARGS) int i; PgStat_SLRUStats *stats; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - - /* Build a tuple descriptor for our result type */ - if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) - elog(ERROR, "return type must be a row type"); - per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, &tupdesc); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c index bd3091bbfb..07cfae983d 100644 --- a/src/backend/utils/adt/varlena.c +++ b/src/backend/utils/adt/varlena.c @@ -24,6 +24,7 @@ #include "common/hashfn.h" #include "common/int.h" #include "common/unicode_norm.h" +#include "funcapi.h" #include "lib/hyperloglog.h" #include "libpq/pqformat.h" #include "miscadmin.h" @@ -4844,22 +4845,18 @@ text_to_table(PG_FUNCTION_ARGS) SplitTextOutputData tstate; MemoryContext old_cxt; - /* check to see if caller supports us returning a tuplestore */ - if (rsi == NULL || !IsA(rsi, ReturnSetInfo)) + if (!rsi->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsi->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); /* OK, prepare tuplestore in per-query memory */ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory); tstate.astate = NULL; tstate.tupdesc = CreateTupleDescCopy(rsi->expectedDesc); - tstate.tupstore = tuplestore_begin_heap(true, false, work_mem); + tstate.tupstore = MakeFuncResultTuplestore(fcinfo, NULL); MemoryContextSwitchTo(old_cxt); diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index e94b8037ec..3787ffe7b3 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -13,6 +13,7 @@ */ #include "postgres.h" +#include "miscadmin.h" #include "access/htup_details.h" #include "access/relation.h" #include "catalog/namespace.h" @@ -1757,7 +1758,38 @@ build_function_result_tupdesc_d(char prokind, return desc; } +/* + * Helper function to construct tuplestore + */ +Tuplestorestate *MakeFuncResultTuplestore(FunctionCallInfo fcinfo, TupleDesc *tupdesc) +{ + Tuplestorestate *tupstore; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + /* Must be called in per query memory context */ + Assert(CurrentMemoryContext == rsinfo->econtext->ecxt_per_query_memory); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* If needed, build a tuple descriptor for our result type */ + if (tupdesc) + { + if (get_call_result_type(fcinfo, NULL, tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + } + tupstore = tuplestore_begin_heap(true, false, work_mem); + + return tupstore; +} /* * RelationNameGetTupleDesc diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index e91d5a3cfd..4e2d537463 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10039,16 +10039,6 @@ show_all_file_settings(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; - /* Check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* Scan the config files using current context as workspace */ conf = ProcessConfigFileInternal(PGC_SIGHUP, false, DEBUG3); @@ -10074,7 +10064,7 @@ show_all_file_settings(PG_FUNCTION_ARGS) TEXTOID, -1, 0); /* Build a tuplestore to return our results in */ - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tupstore; rsinfo->setDesc = tupdesc; diff --git a/src/backend/utils/misc/pg_config.c b/src/backend/utils/misc/pg_config.c index 34d77db75a..6d75d3ebcb 100644 --- a/src/backend/utils/misc/pg_config.c +++ b/src/backend/utils/misc/pg_config.c @@ -36,12 +36,11 @@ pg_config(PG_FUNCTION_ARGS) char *values[2]; int i = 0; - /* check to see if caller supports us returning a tuplestore */ - if (!rsinfo || !(rsinfo->allowedModes & SFRM_Materialize)) + if (!rsinfo->expectedDesc) ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("materialize mode required, but it is not " - "allowed in this context"))); + (errcode(ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED), + errmsg("expected tuple format not specified as required for " + "set-returning function."))); per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); @@ -67,7 +66,7 @@ pg_config(PG_FUNCTION_ARGS) rsinfo->returnMode = SFRM_Materialize; /* initialize our tuplestore */ - tupstore = tuplestore_begin_heap(true, false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); configdata = get_configdata(my_exec_path, &configdata_len); for (i = 0; i < configdata_len; i++) diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index 58674d611d..c68317d038 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -21,6 +21,7 @@ #include "access/xact.h" #include "catalog/pg_type.h" #include "commands/portalcmds.h" +#include "funcapi.h" #include "miscadmin.h" #include "storage/ipc.h" #include "utils/builtins.h" @@ -1138,16 +1139,6 @@ pg_cursor(PG_FUNCTION_ARGS) HASH_SEQ_STATUS hash_seq; PortalHashEnt *hentry; - /* check to see if caller supports us returning a tuplestore */ - if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("set-valued function called in context that cannot accept a set"))); - if (!(rsinfo->allowedModes & SFRM_Materialize)) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("materialize mode required, but it is not allowed in this context"))); - /* need to build tuplestore in query context */ per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; oldcontext = MemoryContextSwitchTo(per_query_ctx); @@ -1174,9 +1165,7 @@ pg_cursor(PG_FUNCTION_ARGS) * We put all the tuples into a tuplestore in one scan of the hashtable. * This avoids any issue of the hashtable possibly changing between calls. */ - tupstore = - tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random, - false, work_mem); + tupstore = MakeFuncResultTuplestore(fcinfo, NULL); /* generate junk in short-term context */ MemoryContextSwitchTo(oldcontext); diff --git a/src/include/funcapi.h b/src/include/funcapi.h index f1304d47e3..003e78fe40 100644 --- a/src/include/funcapi.h +++ b/src/include/funcapi.h @@ -229,6 +229,7 @@ extern TupleDesc BlessTupleDesc(TupleDesc tupdesc); extern AttInMetadata *TupleDescGetAttInMetadata(TupleDesc tupdesc); extern HeapTuple BuildTupleFromCStrings(AttInMetadata *attinmeta, char **values); extern Datum HeapTupleHeaderGetDatum(HeapTupleHeader tuple); +extern Tuplestorestate *MakeFuncResultTuplestore(FunctionCallInfo fcinfo, TupleDesc *result_tupdesc); /*---------- -- 2.30.2