On 15.03.21 14:56, David Steele wrote:
Hi Peter,
On 12/30/20 9:33 AM, Peter Eisentraut wrote:
On 2020-10-09 20:46, Andres Freund wrote:
Is there really a good reason for forcing the client to issue
NextResult, Describe, Execute for each of the dynamic result sets? It's
not like there's really a case for allowing the clients to skip them,
right? Why aren't we sending something more like
S: CommandPartiallyComplete
S: RowDescription
S: DataRow...
S: CommandPartiallyComplete
S: RowDescription
S: DataRow...
...
S: CommandComplete
C: Sync
I want to post my current patch, to keep this discussion moving.
CFBot reports that tests are failing, although the patch applies.
Yes, as explained in the message, you need another patch that makes psql
show the additional result sets. The cfbot cannot handle that kind of
thing.
In the meantime, I have made a few small fixes, so I'm attaching another
patch.
From 163d2ba39a0b46deb83e7509d85a5b2012fd84ec Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Tue, 16 Mar 2021 11:28:53 +0100
Subject: [PATCH v2] Dynamic result sets from procedures
Declaring a cursor WITH RETURN in a procedure makes the cursor's data be
returned as a result of the CALL invocation. The procedure needs to
be declared with the DYNAMIC RESULT SETS attribute.
Discussion:
https://www.postgresql.org/message-id/flat/6e747f98-835f-2e05-cde5-86ee444a7...@2ndquadrant.com
---
doc/src/sgml/catalogs.sgml | 10 +++
doc/src/sgml/information_schema.sgml | 3 +-
doc/src/sgml/protocol.sgml | 19 +++++
doc/src/sgml/ref/alter_procedure.sgml | 12 +++
doc/src/sgml/ref/create_procedure.sgml | 14 +++
doc/src/sgml/ref/declare.sgml | 34 +++++++-
src/backend/catalog/information_schema.sql | 2 +-
src/backend/catalog/pg_aggregate.c | 3 +-
src/backend/catalog/pg_proc.c | 4 +-
src/backend/catalog/sql_features.txt | 2 +-
src/backend/commands/functioncmds.c | 75 ++++++++++++++--
src/backend/commands/portalcmds.c | 23 +++++
src/backend/commands/typecmds.c | 12 ++-
src/backend/parser/gram.y | 20 ++++-
src/backend/tcop/postgres.c | 62 +++++++++++++-
src/backend/tcop/pquery.c | 6 ++
src/backend/utils/errcodes.txt | 1 +
src/backend/utils/mmgr/portalmem.c | 48 +++++++++++
src/bin/pg_dump/pg_dump.c | 16 +++-
src/include/catalog/pg_proc.h | 6 +-
src/include/commands/defrem.h | 2 +
src/include/nodes/parsenodes.h | 9 +-
src/include/parser/kwlist.h | 3 +
src/include/utils/portal.h | 14 +++
src/interfaces/libpq/fe-protocol3.c | 6 +-
.../regress/expected/create_procedure.out | 85 ++++++++++++++++++-
src/test/regress/sql/create_procedure.sql | 61 ++++++++++++-
27 files changed, 518 insertions(+), 34 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b1de6d0674..c30d6328ee 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -5844,6 +5844,16 @@ <title><structname>pg_proc</structname> Columns</title>
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>prodynres</structfield> <type>int4</type>
+ </para>
+ <para>
+ For procedures, this records the maximum number of dynamic result sets
+ the procedure may create. Otherwise zero.
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>pronargs</structfield> <type>int2</type>
diff --git a/doc/src/sgml/information_schema.sgml
b/doc/src/sgml/information_schema.sgml
index 4100198252..7f7498eeff 100644
--- a/doc/src/sgml/information_schema.sgml
+++ b/doc/src/sgml/information_schema.sgml
@@ -5884,7 +5884,8 @@ <title><structname>routines</structname> Columns</title>
<structfield>max_dynamic_result_sets</structfield>
<type>cardinal_number</type>
</para>
<para>
- Applies to a feature not available in
<productname>PostgreSQL</productname>
+ For a procedure, the maximum number of dynamic result sets. Otherwise
+ zero.
</para></entry>
</row>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 43092fe62a..4fe0b271e7 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -959,6 +959,25 @@ <title>Extended Query</title>
an empty query string), ErrorResponse, or PortalSuspended.
</para>
+ <para>
+ Executing a portal may give rise to a <firstterm>dynamic result set
+ sequence</firstterm>. That means the command contained in the portal
+ created additional result sets beyond what it normally returns. (The
+ typical example is calling a stored procedure that creates dynamic result
+ sets.) Dynamic result sets are issues after whatever response the main
+ command issued. Each dynamic result set begins with a RowDescription
+ message followed by zero or more DataRow messages. (Since as explained
+ above an Execute message normally does not respond with a RowDescription,
+ the appearance of the first RowDescription marks the end of the primary
+ result set of the portal and the beginning of the first dynamic result
+ set.) The CommandComplete message that concludes the Execute message
+ response follows <emphasis>after</emphasis> all dynamic result sets. Note
+ that dynamic result sets cannot, by their nature, be decribed prior to the
+ execution of the portal. Multiple executions of the same prepared
+ statement could result in dynamic result sets with different row
+ descriptions being returned.
+ </para>
+
<para>
At completion of each series of extended-query messages, the frontend
should issue a Sync message. This parameterless message causes the
diff --git a/doc/src/sgml/ref/alter_procedure.sgml
b/doc/src/sgml/ref/alter_procedure.sgml
index 9cbe2c7cea..92fc83fae2 100644
--- a/doc/src/sgml/ref/alter_procedure.sgml
+++ b/doc/src/sgml/ref/alter_procedure.sgml
@@ -34,6 +34,7 @@
<phrase>where <replaceable class="parameter">action</replaceable> is one
of:</phrase>
+ DYNAMIC RESULT SETS <replaceable
class="parameter">dynamic_result_sets</replaceable>
[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
SET <replaceable class="parameter">configuration_parameter</replaceable> {
TO | = } { <replaceable class="parameter">value</replaceable> | DEFAULT }
SET <replaceable class="parameter">configuration_parameter</replaceable>
FROM CURRENT
@@ -150,6 +151,17 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>DYNAMIC RESULT SETS <replaceable
class="parameter">dynamic_result_sets</replaceable></literal></term>
+
+ <listitem>
+ <para>
+ Changes the dynamic result sets setting of the procedure. See <xref
+ linkend="sql-createprocedure"/> for more information.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal><optional> EXTERNAL </optional> SECURITY
INVOKER</literal></term>
<term><literal><optional> EXTERNAL </optional> SECURITY
DEFINER</literal></term>
diff --git a/doc/src/sgml/ref/create_procedure.sgml
b/doc/src/sgml/ref/create_procedure.sgml
index 6dbc012719..39ff658469 100644
--- a/doc/src/sgml/ref/create_procedure.sgml
+++ b/doc/src/sgml/ref/create_procedure.sgml
@@ -24,6 +24,7 @@
CREATE [ OR REPLACE ] PROCEDURE
<replaceable class="parameter">name</replaceable> ( [ [ <replaceable
class="parameter">argmode</replaceable> ] [ <replaceable
class="parameter">argname</replaceable> ] <replaceable
class="parameter">argtype</replaceable> [ { DEFAULT | = } <replaceable
class="parameter">default_expr</replaceable> ] [, ...] ] )
{ LANGUAGE <replaceable class="parameter">lang_name</replaceable>
+ | DYNAMIC RESULT SETS <replaceable
class="parameter">dynamic_result_sets</replaceable>
| TRANSFORM { FOR TYPE <replaceable
class="parameter">type_name</replaceable> } [, ... ]
| [ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER
| SET <replaceable class="parameter">configuration_parameter</replaceable>
{ TO <replaceable class="parameter">value</replaceable> | = <replaceable
class="parameter">value</replaceable> | FROM CURRENT }
@@ -173,6 +174,19 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>DYNAMIC RESULT SETS <replaceable
class="parameter">dynamic_result_sets</replaceable></literal></term>
+
+ <listitem>
+ <para>
+ Specifies how many dynamic result sets the procedure returns (see
+ <literal><link linkend="sql-declare">DECLARE</link> WITH
+ RETURN</literal>). The default is 0. If a procedure returns more
+ result sets than declared, a warning is raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>TRANSFORM { FOR TYPE <replaceable
class="parameter">type_name</replaceable> } [, ... ] }</literal></term>
diff --git a/doc/src/sgml/ref/declare.sgml b/doc/src/sgml/ref/declare.sgml
index 2152134635..dded159d18 100644
--- a/doc/src/sgml/ref/declare.sgml
+++ b/doc/src/sgml/ref/declare.sgml
@@ -27,7 +27,8 @@
<refsynopsisdiv>
<synopsis>
DECLARE <replaceable class="parameter">name</replaceable> [ BINARY ] [
INSENSITIVE ] [ [ NO ] SCROLL ]
- CURSOR [ { WITH | WITHOUT } HOLD ] FOR <replaceable
class="parameter">query</replaceable>
+ CURSOR [ { WITH | WITHOUT } HOLD ] [ { WITH | WITHOUT } RETURN ]
+ FOR <replaceable class="parameter">query</replaceable>
</synopsis>
</refsynopsisdiv>
@@ -120,6 +121,22 @@ <title>Parameters</title>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><literal>WITH RETURN</literal></term>
+ <term><literal>WITHOUT RETURN</literal></term>
+ <listitem>
+ <para>
+ This option is only valid for cursors defined inside a procedure.
+ <literal>WITH RETURN</literal> specifies that the cursor's result rows
+ will be provided as a result set of the procedure invocation. To
+ accomplish that, the cursor must be left open at the end of the
+ procedure. If multiple <literal>WITH RETURN</literal> cursors are
+ declared, then their results will be returned in the order they were
+ created. <literal>WITHOUT RETURN</literal> is the default.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><replaceable class="parameter">query</replaceable></term>
<listitem>
@@ -313,6 +330,21 @@ <title>Examples</title>
See <xref linkend="sql-fetch"/> for more
examples of cursor usage.
</para>
+
+ <para>
+ This example shows how to return multiple result sets from a procedure:
+<programlisting>
+CREATE PROCEDURE test()
+LANGUAGE SQL
+AS $$
+DECLARE a CURSOR WITH RETURN FOR SELECT * FROM tbl1;
+DECLARE b CURSOR WITH RETURN FOR SELECT * FROM tbl2;
+$$;
+
+CALL test();
+</programlisting>
+ The results of the two cursors will be returned in order from this call.
+ </para>
</refsect1>
<refsect1>
diff --git a/src/backend/catalog/information_schema.sql
b/src/backend/catalog/information_schema.sql
index 513cb9a69c..c88a1c4de4 100644
--- a/src/backend/catalog/information_schema.sql
+++ b/src/backend/catalog/information_schema.sql
@@ -1586,7 +1586,7 @@ CREATE VIEW routines AS
CASE WHEN p.proisstrict THEN 'YES' ELSE 'NO' END END AS
yes_or_no) AS is_null_call,
CAST(null AS character_data) AS sql_path,
CAST('YES' AS yes_or_no) AS schema_level_routine,
- CAST(0 AS cardinal_number) AS max_dynamic_result_sets,
+ CAST(p.prodynres AS cardinal_number) AS max_dynamic_result_sets,
CAST(null AS yes_or_no) AS is_user_defined_cast,
CAST(null AS yes_or_no) AS is_implicitly_invocable,
CAST(CASE WHEN p.prosecdef THEN 'DEFINER' ELSE 'INVOKER' END AS
character_data) AS security_type,
diff --git a/src/backend/catalog/pg_aggregate.c
b/src/backend/catalog/pg_aggregate.c
index 89f23d0add..e557d43942 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -639,7 +639,8 @@ AggregateCreate(const char *aggName,
PointerGetDatum(NULL),
/* proconfig */
InvalidOid, /* no
prosupport */
1, /* procost */
- 0); /* prorows */
+ 0, /* prorows */
+ 0); /* prodynres */
procOid = myself.objectId;
/*
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index e14eee5a19..05033393a5 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -91,7 +91,8 @@ ProcedureCreate(const char *procedureName,
Datum proconfig,
Oid prosupport,
float4 procost,
- float4 prorows)
+ float4 prorows,
+ int dynres)
{
Oid retval;
int parameterCount;
@@ -310,6 +311,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_proretset - 1] = BoolGetDatum(returnsSet);
values[Anum_pg_proc_provolatile - 1] = CharGetDatum(volatility);
values[Anum_pg_proc_proparallel - 1] = CharGetDatum(parallel);
+ values[Anum_pg_proc_prodynres - 1] = Int32GetDatum(dynres);
values[Anum_pg_proc_pronargs - 1] = UInt16GetDatum(parameterCount);
values[Anum_pg_proc_pronargdefaults - 1] =
UInt16GetDatum(list_length(parameterDefaults));
values[Anum_pg_proc_prorettype - 1] = ObjectIdGetDatum(returnType);
diff --git a/src/backend/catalog/sql_features.txt
b/src/backend/catalog/sql_features.txt
index 32eed988ab..94d0a4494f 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -485,7 +485,7 @@ T433 Multiargument GROUPING function
YES
T434 GROUP BY DISTINCT NO
T441 ABS and MOD functions YES
T461 Symmetric BETWEEN predicate YES
-T471 Result sets return value NO
+T471 Result sets return value NO partially
supported
T472 DESCRIBE CURSOR NO
T491 LATERAL derived table YES
T495 Combined data change and retrieval NO
different syntax
diff --git a/src/backend/commands/functioncmds.c
b/src/backend/commands/functioncmds.c
index 7a4e104623..437bddd401 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -68,6 +68,7 @@
#include "utils/guc.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
+#include "utils/portal.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
@@ -478,7 +479,8 @@ compute_common_attribute(ParseState *pstate,
DefElem **cost_item,
DefElem **rows_item,
DefElem **support_item,
- DefElem **parallel_item)
+ DefElem **parallel_item,
+ DefElem **dynres_item)
{
if (strcmp(defel->defname, "volatility") == 0)
{
@@ -554,6 +556,15 @@ compute_common_attribute(ParseState *pstate,
*parallel_item = defel;
}
+ else if (strcmp(defel->defname, "dynamic_result_sets") == 0)
+ {
+ if (!is_procedure)
+ goto function_error;
+ if (*dynres_item)
+ goto duplicate_error;
+
+ *dynres_item = defel;
+ }
else
return false;
@@ -567,6 +578,13 @@ compute_common_attribute(ParseState *pstate,
parser_errposition(pstate, defel->location)));
return false; /* keep compiler quiet */
+function_error:
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("invalid attribute in function definition"),
+ parser_errposition(pstate, defel->location)));
+ return false;
+
procedure_error:
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
@@ -703,7 +721,8 @@ compute_function_attributes(ParseState *pstate,
float4 *procost,
float4 *prorows,
Oid *prosupport,
- char *parallel_p)
+ char *parallel_p,
+ int *dynres_p)
{
ListCell *option;
DefElem *as_item = NULL;
@@ -719,6 +738,7 @@ compute_function_attributes(ParseState *pstate,
DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL;
+ DefElem *dynres_item = NULL;
foreach(option, options)
{
@@ -776,7 +796,8 @@ compute_function_attributes(ParseState *pstate,
&cost_item,
&rows_item,
&support_item,
-
¶llel_item))
+
¶llel_item,
+
&dynres_item))
{
/* recognized common option */
continue;
@@ -842,6 +863,11 @@ compute_function_attributes(ParseState *pstate,
*prosupport = interpret_func_support(support_item);
if (parallel_item)
*parallel_p = interpret_func_parallel(parallel_item);
+ if (dynres_item)
+ {
+ *dynres_p = intVal(dynres_item->arg);
+ Assert(*dynres_p >= 0); /* enforced by parser */
+ }
}
@@ -950,6 +976,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
Form_pg_language languageStruct;
List *as_clause;
char parallel;
+ int dynres;
/* Convert list of names to a name and namespace */
namespaceId = QualifiedNameGetCreationNamespace(stmt->funcname,
@@ -972,6 +999,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
prorows = -1; /* indicates not set */
prosupport = InvalidOid;
parallel = PROPARALLEL_UNSAFE;
+ dynres = 0;
/* Extract non-default attributes from stmt->options list */
compute_function_attributes(pstate,
@@ -981,7 +1009,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt
*stmt)
&isWindowFunc,
&volatility,
&isStrict,
&security, &isLeakProof,
&proconfig,
&procost, &prorows,
- &prosupport,
¶llel);
+ &prosupport,
¶llel, &dynres);
/* Look up the language and validate permissions */
languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
@@ -1170,7 +1198,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt
*stmt)
PointerGetDatum(proconfig),
prosupport,
procost,
- prorows);
+ prorows,
+ dynres);
}
/*
@@ -1245,6 +1274,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL;
+ DefElem *dynres_item = NULL;
ObjectAddress address;
rel = table_open(ProcedureRelationId, RowExclusiveLock);
@@ -1288,7 +1318,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
&cost_item,
&rows_item,
&support_item,
-
¶llel_item) == false)
+
¶llel_item,
+
&dynres_item) == false)
elog(ERROR, "option \"%s\" not recognized",
defel->defname);
}
@@ -1384,6 +1415,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
}
if (parallel_item)
procForm->proparallel = interpret_func_parallel(parallel_item);
+ if (dynres_item)
+ procForm->prodynres = intVal(dynres_item->arg);
/* Do the update */
CatalogTupleUpdate(rel, &tup->t_self, tup);
@@ -2027,6 +2060,24 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic)
OidFunctionCall1(laninline, PointerGetDatum(codeblock));
}
+static List *procedure_stack;
+
+Oid
+CurrentProcedure(void)
+{
+ if (!procedure_stack)
+ return InvalidOid;
+ else
+ return llast_oid(procedure_stack);
+}
+
+void
+ProcedureCallsCleanup(void)
+{
+ /* The content of this is freed by memory cleanup. */
+ procedure_stack = NIL;
+}
+
/*
* Execute CALL statement
*
@@ -2069,6 +2120,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params,
bool atomic, DestReceiver
char *argmodes;
FmgrInfo flinfo;
CallContext *callcontext;
+ int prodynres;
EState *estate;
ExprContext *econtext;
HeapTuple tp;
@@ -2109,6 +2161,8 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params,
bool atomic, DestReceiver
if (((Form_pg_proc) GETSTRUCT(tp))->prosecdef)
callcontext->atomic = true;
+ prodynres = ((Form_pg_proc) GETSTRUCT(tp))->prodynres;
+
/*
* Expand named arguments, defaults, etc. We do not want to scribble on
* the passed-in CallStmt parse tree, so first flat-copy fexpr, allowing
@@ -2180,9 +2234,11 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params,
bool atomic, DestReceiver
i++;
}
+ procedure_stack = lappend_oid(procedure_stack, fexpr->funcid);
pgstat_init_function_usage(fcinfo, &fcusage);
retval = FunctionCallInvoke(fcinfo);
pgstat_end_function_usage(&fcusage, true);
+ procedure_stack = list_delete_last(procedure_stack);
if (fexpr->funcresulttype == VOIDOID)
{
@@ -2230,6 +2286,13 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params,
bool atomic, DestReceiver
fexpr->funcresulttype);
FreeExecutorState(estate);
+
+ CloseOtherReturnableCursors(fexpr->funcid);
+
+ if (list_length(GetReturnableCursors()) > prodynres)
+ ereport(WARNING,
+
errcode(ERRCODE_WARNING_ATTEMPT_TO_RETURN_TOO_MANY_RESULT_SETS),
+ errmsg("attempt to return too many result
sets"));
}
/*
diff --git a/src/backend/commands/portalcmds.c
b/src/backend/commands/portalcmds.c
index 6f2397bd36..cb7aacd5ce 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -24,6 +24,7 @@
#include <limits.h>
#include "access/xact.h"
+#include "commands/defrem.h"
#include "commands/portalcmds.h"
#include "executor/executor.h"
#include "executor/tstoreReceiver.h"
@@ -146,6 +147,28 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt
*cstmt, ParamListInfo pa
portal->cursorOptions |= CURSOR_OPT_NO_SCROLL;
}
+ /*
+ * For returnable cursors, remember the currently active procedure, as
+ * well as the command ID, so we can sort by creation order later. If
+ * there is no procedure active, the cursor is marked as WITHOUT RETURN.
+ * (This is not an error, per SQL standard, subclause "Effect of
opening a
+ * cursor".)
+ */
+ if (portal->cursorOptions & CURSOR_OPT_RETURN)
+ {
+ Oid procId = CurrentProcedure();
+
+ if (procId)
+ {
+ portal->procId = procId;
+ portal->createCid = GetCurrentCommandId(true);
+ }
+ else
+ {
+ portal->cursorOptions &= ~CURSOR_OPT_RETURN;
+ }
+ }
+
/*
* Start execution, inserting parameters if any.
*/
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 76218fb47e..e95105574b 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1790,7 +1790,8 @@ makeRangeConstructors(const char *name, Oid namespace,
PointerGetDatum(NULL), /* proconfig */
InvalidOid,
/* prosupport */
1.0, /*
procost */
- 0.0); /*
prorows */
+ 0.0, /*
prorows */
+ 0); /*
prodynres */
/*
* Make the constructors internally-dependent on the range type
so
@@ -1854,7 +1855,8 @@ makeMultirangeConstructors(const char *name, Oid
namespace,
PointerGetDatum(NULL),
/* proconfig */
InvalidOid, /*
prosupport */
1.0, /* procost */
- 0.0); /* prorows */
+ 0.0, /* prorows */
+ 0); /* prodynres */
/*
* Make the constructor internally-dependent on the multirange type so
@@ -1897,7 +1899,8 @@ makeMultirangeConstructors(const char *name, Oid
namespace,
PointerGetDatum(NULL),
/* proconfig */
InvalidOid, /*
prosupport */
1.0, /* procost */
- 0.0); /* prorows */
+ 0.0, /* prorows */
+ 0); /* prodynres */
/* ditto */
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
pfree(argtypes);
@@ -1937,7 +1940,8 @@ makeMultirangeConstructors(const char *name, Oid
namespace,
PointerGetDatum(NULL),
/* proconfig */
InvalidOid, /*
prosupport */
1.0, /* procost */
- 0.0); /* prorows */
+ 0.0, /* prorows */
+ 0); /* prodynres */
/* ditto */
recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
pfree(argtypes);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 652be0b96d..9e13424ff4 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -640,7 +640,7 @@ static Node *makeRecursiveViewSelect(char *relname, List
*aliases, Node *query);
DATA_P DATABASE DAY_P DEALLOCATE DEC DECIMAL_P DECLARE DEFAULT DEFAULTS
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DEPENDS DEPTH
DESC
DETACH DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P
- DOUBLE_P DROP
+ DOUBLE_P DROP DYNAMIC
EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXPRESSION
@@ -685,7 +685,7 @@ static Node *makeRecursiveViewSelect(char *relname, List
*aliases, Node *query);
RANGE READ REAL REASSIGN RECHECK RECURSIVE REF REFERENCES REFERENCING
REFRESH REINDEX RELATIVE_P RELEASE RENAME REPEATABLE REPLACE REPLICA
- RESET RESTART RESTRICT RETURNING RETURNS REVOKE RIGHT ROLE ROLLBACK
ROLLUP
+ RESET RESTART RESTRICT RESULT RETURN RETURNING RETURNS REVOKE RIGHT
ROLE ROLLBACK ROLLUP
ROUTINE ROUTINES ROW ROWS RULE
SAVEPOINT SCHEMA SCHEMAS SCROLL SEARCH SECOND_P SECURITY SELECT
SEQUENCE SEQUENCES
@@ -7834,6 +7834,10 @@ common_func_opt_item:
{
$$ = makeDefElem("parallel", (Node
*)makeString($2), @1);
}
+ | DYNAMIC RESULT SETS Iconst
+ {
+ $$ = makeDefElem("dynamic_result_sets",
(Node *)makeInteger($4), @1);
+ }
;
createfunc_opt_item:
@@ -11125,6 +11129,12 @@ cursor_options: /*EMPTY*/
{ $$ = 0; }
opt_hold: /* EMPTY */ { $$ = 0; }
| WITH HOLD
{ $$ = CURSOR_OPT_HOLD; }
| WITHOUT HOLD { $$ =
0; }
+ | WITH HOLD WITH RETURN { $$ =
CURSOR_OPT_HOLD | CURSOR_OPT_RETURN; }
+ | WITHOUT HOLD WITH RETURN { $$ =
CURSOR_OPT_RETURN; }
+ | WITH HOLD WITHOUT RETURN { $$ =
CURSOR_OPT_HOLD; }
+ | WITHOUT HOLD WITHOUT RETURN { $$ = 0; }
+ | WITH RETURN { $$ =
CURSOR_OPT_RETURN; }
+ | WITHOUT RETURN { $$ =
0; }
;
/*****************************************************************************
@@ -15331,6 +15341,7 @@ unreserved_keyword:
| DOMAIN_P
| DOUBLE_P
| DROP
+ | DYNAMIC
| EACH
| ENABLE_P
| ENCODING
@@ -15472,6 +15483,8 @@ unreserved_keyword:
| RESET
| RESTART
| RESTRICT
+ | RESULT
+ | RETURN
| RETURNS
| REVOKE
| ROLE
@@ -15867,6 +15880,7 @@ bare_label_keyword:
| DOMAIN_P
| DOUBLE_P
| DROP
+ | DYNAMIC
| EACH
| ELSE
| ENABLE_P
@@ -16050,6 +16064,8 @@ bare_label_keyword:
| RESET
| RESTART
| RESTRICT
+ | RESULT
+ | RETURN
| RETURNS
| REVOKE
| RIGHT
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 8a0332dde9..56dc35c68e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -41,6 +41,7 @@
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "commands/async.h"
+#include "commands/defrem.h"
#include "commands/prepare.h"
#include "executor/spi.h"
#include "jit/jit.h"
@@ -1009,6 +1010,7 @@ exec_simple_query(const char *query_string)
Portal portal;
DestReceiver *receiver;
int16 format;
+ ListCell *lc;
/*
* Get the command name for use in status display (it also
becomes the
@@ -1168,7 +1170,7 @@ exec_simple_query(const char *query_string)
MemoryContextSwitchTo(oldcontext);
/*
- * Run the portal to completion, and then drop it (and the
receiver).
+ * Run the portal to completion, and then drop it.
*/
(void) PortalRun(portal,
FETCH_ALL,
@@ -1178,10 +1180,34 @@ exec_simple_query(const char *query_string)
receiver,
&qc);
- receiver->rDestroy(receiver);
-
PortalDrop(portal, false);
+ /*
+ * Run portals for dynamic result sets.
+ */
+ foreach (lc, GetReturnableCursors())
+ {
+ Portal portal = lfirst(lc);
+
+ if (dest == DestRemote)
+ SetRemoteDestReceiverParams(receiver, portal);
+
+ PortalRun(portal,
+ FETCH_ALL,
+ true,
+ true,
+ receiver,
+ receiver,
+ NULL);
+
+ PortalDrop(portal, false);
+ }
+
+ /*
+ * Drop the receiver.
+ */
+ receiver->rDestroy(receiver);
+
if (lnext(parsetree_list, parsetree_item) == NULL)
{
/*
@@ -1982,6 +2008,7 @@ exec_execute_message(const char *portal_name, long
max_rows)
const char *sourceText;
const char *prepStmtName;
ParamListInfo portalParams;
+ ListCell *lc;
bool save_log_statement_stats = log_statement_stats;
bool is_xact_command;
bool execute_is_fetch;
@@ -2134,6 +2161,34 @@ exec_execute_message(const char *portal_name, long
max_rows)
receiver,
&qc);
+ /*
+ * Run portals for dynamic result sets.
+ */
+ foreach (lc, GetReturnableCursors())
+ {
+ Portal dyn_portal = lfirst(lc);
+
+ if (dest == DestRemote)
+ SetRemoteDestReceiverParams(receiver, dyn_portal);
+
+ PortalSetResultFormat(dyn_portal, 1, &portal->dynamic_format);
+
+ SendRowDescriptionMessage(&row_description_buf,
+
dyn_portal->tupDesc,
+
FetchPortalTargetList(dyn_portal),
+
dyn_portal->formats);
+
+ PortalRun(dyn_portal,
+ FETCH_ALL,
+ true,
+ true,
+ receiver,
+ receiver,
+ NULL);
+
+ PortalDrop(dyn_portal, false);
+ }
+
receiver->rDestroy(receiver);
/* Done executing; remove the params error callback */
@@ -4083,6 +4138,7 @@ PostgresMain(int argc, char *argv[],
PortalErrorCleanup();
SPICleanup();
+ ProcedureCallsCleanup();
/*
* We can't release replication slots inside AbortTransaction()
as we
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 44f5fe8fc9..4d6be24e30 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -629,6 +629,8 @@ PortalSetResultFormat(Portal portal, int nFormats, int16
*formats)
errmsg("bind message has %d result
formats but query has %d columns",
nFormats, natts)));
memcpy(portal->formats, formats, natts * sizeof(int16));
+
+ portal->dynamic_format = 0;
}
else if (nFormats > 0)
{
@@ -637,12 +639,16 @@ PortalSetResultFormat(Portal portal, int nFormats, int16
*formats)
for (i = 0; i < natts; i++)
portal->formats[i] = format1;
+
+ portal->dynamic_format = format1;
}
else
{
/* use default format for all columns */
for (i = 0; i < natts; i++)
portal->formats[i] = 0;
+
+ portal->dynamic_format = 0;
}
}
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 9874a77805..a4504b6436 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -83,6 +83,7 @@ Section: Class 01 - Warning
# do not use this class for failure conditions
01000 W ERRCODE_WARNING
warning
0100C W ERRCODE_WARNING_DYNAMIC_RESULT_SETS_RETURNED
dynamic_result_sets_returned
+0100E W ERRCODE_WARNING_ATTEMPT_TO_RETURN_TOO_MANY_RESULT_SETS
attempt_to_return_too_many_result_sets
01008 W ERRCODE_WARNING_IMPLICIT_ZERO_BIT_PADDING
implicit_zero_bit_padding
01003 W ERRCODE_WARNING_NULL_VALUE_ELIMINATED_IN_SET_FUNCTION
null_value_eliminated_in_set_function
01007 W ERRCODE_WARNING_PRIVILEGE_NOT_GRANTED
privilege_not_granted
diff --git a/src/backend/utils/mmgr/portalmem.c
b/src/backend/utils/mmgr/portalmem.c
index 66e3181815..395684bf54 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -1278,3 +1278,51 @@ HoldPinnedPortals(void)
}
}
}
+
+static int
+cmp_portals_by_creation(const ListCell *a, const ListCell *b)
+{
+ Portal pa = lfirst(a);
+ Portal pb = lfirst(b);
+
+ return pa->createCid - pb->createCid;
+}
+
+List *
+GetReturnableCursors(void)
+{
+ List *ret = NIL;
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->cursorOptions & CURSOR_OPT_RETURN)
+ ret = lappend(ret, portal);
+ }
+
+ list_sort(ret, cmp_portals_by_creation);
+
+ return ret;
+}
+
+void
+CloseOtherReturnableCursors(Oid procid)
+{
+ HASH_SEQ_STATUS status;
+ PortalHashEnt *hentry;
+
+ hash_seq_init(&status, PortalHashTable);
+
+ while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+ {
+ Portal portal = hentry->portal;
+
+ if (portal->cursorOptions & CURSOR_OPT_RETURN && portal->procId
!= procid)
+ PortalDrop(portal, false);
+ }
+}
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index eb988d7eb4..787edc92a2 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11996,6 +11996,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
char *prorows;
char *prosupport;
char *proparallel;
+ int prodynres;
char *lanname;
char *rettypename;
int nallargs;
@@ -12090,10 +12091,17 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (fout->remoteVersion >= 120000)
appendPQExpBufferStr(query,
- "prosupport\n");
+ "prosupport,\n");
else
appendPQExpBufferStr(query,
- "'-' AS prosupport\n");
+ "'-' AS
prosupport,\n");
+
+ if (fout->remoteVersion >= 140000)
+ appendPQExpBufferStr(query,
+ "prodynres\n");
+ else
+ appendPQExpBufferStr(query,
+ "0 AS prodynres\n");
appendPQExpBuffer(query,
"FROM pg_catalog.pg_proc "
@@ -12133,6 +12141,7 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
prosupport = PQgetvalue(res, 0, PQfnumber(res, "prosupport"));
proparallel = PQgetvalue(res, 0, PQfnumber(res, "proparallel"));
+ prodynres = atoi(PQgetvalue(res, 0, PQfnumber(res, "prodynres")));
lanname = PQgetvalue(res, 0, PQfnumber(res, "lanname"));
/*
@@ -12309,6 +12318,9 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
if (proisstrict[0] == 't')
appendPQExpBufferStr(q, " STRICT");
+ if (prodynres > 0)
+ appendPQExpBuffer(q, " DYNAMIC RESULT SETS %d", prodynres);
+
if (prosecdef[0] == 't')
appendPQExpBufferStr(q, " SECURITY DEFINER");
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index 78f230894b..7fa4d099bd 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -76,6 +76,9 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP
BKI_ROWTYPE_OID(81,Proce
/* see PROPARALLEL_ categories below */
char proparallel BKI_DEFAULT(s);
+ /* maximum number of dynamic result sets */
+ int32 prodynres BKI_DEFAULT(0);
+
/* number of arguments */
/* Note: need not be given in pg_proc.dat; genbki.pl will compute it */
int16 pronargs;
@@ -209,7 +212,8 @@ extern ObjectAddress ProcedureCreate(const char
*procedureName,
Datum
proconfig,
Oid
prosupport,
float4
procost,
- float4
prorows);
+ float4
prorows,
+ int
dynres);
extern bool function_parse_error_transpose(const char *prosrc);
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 1a79540c94..030a7d9011 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -57,6 +57,8 @@ extern ObjectAddress CreateTransform(CreateTransformStmt
*stmt);
extern void IsThereFunctionInNamespace(const char *proname, int pronargs,
oidvector *proargtypes, Oid nspOid);
extern void ExecuteDoStmt(DoStmt *stmt, bool atomic);
+extern Oid CurrentProcedure(void);
+extern void ProcedureCallsCleanup(void);
extern void ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic,
DestReceiver *dest);
extern TupleDesc CallStmtResultDesc(CallStmt *stmt);
extern Oid get_transform_oid(Oid type_id, Oid lang_id, bool missing_ok);
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 236832a2ca..f335ecba07 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2752,11 +2752,12 @@ typedef struct SecLabelStmt
#define CURSOR_OPT_NO_SCROLL 0x0004 /* NO SCROLL explicitly given */
#define CURSOR_OPT_INSENSITIVE 0x0008 /* INSENSITIVE */
#define CURSOR_OPT_HOLD 0x0010 /* WITH HOLD */
+#define CURSOR_OPT_RETURN 0x0020 /* WITH RETURN */
/* these planner-control flags do not correspond to any SQL grammar: */
-#define CURSOR_OPT_FAST_PLAN 0x0020 /* prefer fast-start plan */
-#define CURSOR_OPT_GENERIC_PLAN 0x0040 /* force use of generic plan */
-#define CURSOR_OPT_CUSTOM_PLAN 0x0080 /* force use of custom plan */
-#define CURSOR_OPT_PARALLEL_OK 0x0100 /* parallel mode OK */
+#define CURSOR_OPT_FAST_PLAN 0x0100 /* prefer fast-start plan */
+#define CURSOR_OPT_GENERIC_PLAN 0x0200 /* force use of generic plan */
+#define CURSOR_OPT_CUSTOM_PLAN 0x0400 /* force use of custom plan */
+#define CURSOR_OPT_PARALLEL_OK 0x0800 /* parallel mode OK */
typedef struct DeclareCursorStmt
{
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 28083aaac9..9e1f81ac7d 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -141,6 +141,7 @@ PG_KEYWORD("document", DOCUMENT_P, UNRESERVED_KEYWORD,
BARE_LABEL)
PG_KEYWORD("domain", DOMAIN_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("double", DOUBLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("drop", DROP, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("dynamic", DYNAMIC, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("each", EACH, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("else", ELSE, RESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("enable", ENABLE_P, UNRESERVED_KEYWORD, BARE_LABEL)
@@ -346,6 +347,8 @@ PG_KEYWORD("replica", REPLICA, UNRESERVED_KEYWORD,
BARE_LABEL)
PG_KEYWORD("reset", RESET, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restart", RESTART, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("restrict", RESTRICT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("result", RESULT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("return", RETURN, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("returning", RETURNING, RESERVED_KEYWORD, AS_LABEL)
PG_KEYWORD("returns", RETURNS, UNRESERVED_KEYWORD, BARE_LABEL)
PG_KEYWORD("revoke", REVOKE, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 3c17b039cc..97a9e9bf53 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -131,6 +131,16 @@ typedef struct PortalData
SubTransactionId createSubid; /* the creating subxact */
SubTransactionId activeSubid; /* the last subxact with activity */
+ /*
+ * Procedure that created this portal. Used for returnable cursors.
+ */
+ Oid procId;
+ /*
+ * Command ID where the portal was created. Used for sorting returnable
+ * cursors into creation order.
+ */
+ CommandId createCid;
+
/* The query or queries the portal will execute */
const char *sourceText; /* text of query (as of 8.4, never
NULL) */
CommandTag commandTag; /* command tag for original
query */
@@ -159,6 +169,8 @@ typedef struct PortalData
TupleDesc tupDesc; /* descriptor for result tuples
*/
/* and these are the format codes to use for the columns: */
int16 *formats; /* a format code for each column */
+ /* Format code for dynamic result sets */
+ int16 dynamic_format;
/*
* Where we store tuples for a held cursor or a PORTAL_ONE_RETURNING or
@@ -237,5 +249,7 @@ extern void PortalCreateHoldStore(Portal portal);
extern void PortalHashTableDeleteAll(void);
extern bool ThereAreNoReadyPortals(void);
extern void HoldPinnedPortals(void);
+extern List *GetReturnableCursors(void);
+extern void CloseOtherReturnableCursors(Oid procid);
#endif /* PORTAL_H */
diff --git a/src/interfaces/libpq/fe-protocol3.c
b/src/interfaces/libpq/fe-protocol3.c
index 306e89acfd..ebd38c391e 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -337,10 +337,8 @@ pqParseInput3(PGconn *conn)
{
/*
* A new 'T' message is treated
as the start of
- * another PGresult. (It is
not clear that this is
- * really possible with the
current backend.) We stop
- * parsing until the
application accepts the current
- * result.
+ * another PGresult. We stop
parsing until the
+ * application accepts the
current result.
*/
conn->asyncStatus =
PGASYNC_READY;
return;
diff --git a/src/test/regress/expected/create_procedure.out
b/src/test/regress/expected/create_procedure.out
index 3838fa2324..39f134bc82 100644
--- a/src/test/regress/expected/create_procedure.out
+++ b/src/test/regress/expected/create_procedure.out
@@ -212,8 +212,91 @@ ALTER ROUTINE cp_testfunc1a RENAME TO cp_testfunc1;
ALTER ROUTINE ptest1(text) RENAME TO ptest1a;
ALTER ROUTINE ptest1a RENAME TO ptest1;
DROP ROUTINE cp_testfunc1(int);
+-- dynamic result sets
+CREATE TABLE cp_test2 (a int);
+INSERT INTO cp_test2 VALUES (1), (2), (3);
+CREATE TABLE cp_test3 (x text, y text);
+INSERT INTO cp_test3 VALUES ('abc', 'def'), ('foo', 'bar');
+CREATE PROCEDURE pdrstest1()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 2
+AS $$
+DECLARE c1 CURSOR WITH RETURN FOR SELECT * FROM cp_test2;
+DECLARE c2 CURSOR WITH RETURN FOR SELECT * FROM cp_test3;
+$$;
+CALL pdrstest1();
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+ x | y
+-----+-----
+ abc | def
+ foo | bar
+(2 rows)
+
+CREATE PROCEDURE pdrstest2()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+CALL pdrstest1();
+DECLARE c3 CURSOR WITH RETURN FOR SELECT * FROM cp_test2 WHERE a < 2;
+$$;
+CALL pdrstest2();
+ a
+---
+ 1
+(1 row)
+
+CREATE PROCEDURE pdrstest3(INOUT a text)
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+DECLARE c4 CURSOR WITH RETURN FOR SELECT * FROM cp_test2;
+SELECT a || a;
+$$;
+CALL pdrstest3('x');
+ a
+----
+ xx
+(1 row)
+
+ a
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+-- test the nested error handling
+CREATE TABLE cp_test_dummy (a int);
+CREATE PROCEDURE pdrstest4a()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+DECLARE c5a CURSOR WITH RETURN FOR SELECT * FROM cp_test_dummy;
+$$;
+CREATE PROCEDURE pdrstest4b()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+CALL pdrstest4a();
+$$;
+DROP TABLE cp_test_dummy;
+CALL pdrstest4b();
+ERROR: relation "cp_test_dummy" does not exist
+LINE 2: DECLARE c5a CURSOR WITH RETURN FOR SELECT * FROM cp_test_dum...
+ ^
+QUERY:
+DECLARE c5a CURSOR WITH RETURN FOR SELECT * FROM cp_test_dummy;
+
+CONTEXT: SQL function "pdrstest4a" during startup
+SQL function "pdrstest4b" statement 1
-- cleanup
DROP PROCEDURE ptest1;
DROP PROCEDURE ptest2;
-DROP TABLE cp_test;
+DROP TABLE cp_test, cp_test2, cp_test3;
DROP USER regress_cp_user1;
diff --git a/src/test/regress/sql/create_procedure.sql
b/src/test/regress/sql/create_procedure.sql
index 2ef1c82cea..761334df5b 100644
--- a/src/test/regress/sql/create_procedure.sql
+++ b/src/test/regress/sql/create_procedure.sql
@@ -167,11 +167,70 @@ CREATE USER regress_cp_user1;
DROP ROUTINE cp_testfunc1(int);
+-- dynamic result sets
+
+CREATE TABLE cp_test2 (a int);
+INSERT INTO cp_test2 VALUES (1), (2), (3);
+CREATE TABLE cp_test3 (x text, y text);
+INSERT INTO cp_test3 VALUES ('abc', 'def'), ('foo', 'bar');
+
+CREATE PROCEDURE pdrstest1()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 2
+AS $$
+DECLARE c1 CURSOR WITH RETURN FOR SELECT * FROM cp_test2;
+DECLARE c2 CURSOR WITH RETURN FOR SELECT * FROM cp_test3;
+$$;
+
+CALL pdrstest1();
+
+CREATE PROCEDURE pdrstest2()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+CALL pdrstest1();
+DECLARE c3 CURSOR WITH RETURN FOR SELECT * FROM cp_test2 WHERE a < 2;
+$$;
+
+CALL pdrstest2();
+
+CREATE PROCEDURE pdrstest3(INOUT a text)
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+DECLARE c4 CURSOR WITH RETURN FOR SELECT * FROM cp_test2;
+SELECT a || a;
+$$;
+
+CALL pdrstest3('x');
+
+-- test the nested error handling
+CREATE TABLE cp_test_dummy (a int);
+
+CREATE PROCEDURE pdrstest4a()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+DECLARE c5a CURSOR WITH RETURN FOR SELECT * FROM cp_test_dummy;
+$$;
+
+CREATE PROCEDURE pdrstest4b()
+LANGUAGE SQL
+DYNAMIC RESULT SETS 1
+AS $$
+CALL pdrstest4a();
+$$;
+
+DROP TABLE cp_test_dummy;
+
+CALL pdrstest4b();
+
+
-- cleanup
DROP PROCEDURE ptest1;
DROP PROCEDURE ptest2;
-DROP TABLE cp_test;
+DROP TABLE cp_test, cp_test2, cp_test3;
DROP USER regress_cp_user1;
--
2.30.2