diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6c4359d..c9b0d28 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -16803,6 +16803,113 @@ SELECT * FROM pg_ls_dir('.') WITH ORDINALITY AS t(ls,n);
+
+ Cursor Manipulation Functions
+
+
+ This section describes functions that cursors to be manipulated
+ in normal SELECT queries.
+
+
+
+ The creation of cursors using
+ or within a procedural language such as
+ PL/pgSQL
+ (see ), and other
+ manipulaton outside of a SELECT query is
+ described elsewhere in this manual.
+
+
+
+ ROWS_IN(cursor)
+
+
+ cursor manipulation functions
+ functions
+
+
+
+ rows_in
+
+
+
+ROWS_IN(cursor REFCURSOR)
+ RETURNS SETOF RECORD
+
+
+
+ ROWS_IN retrieves rows from a previously-created
+ cursor, or a REFCURSOR returned by a function. It may be
+ considered as equivalent to FETCH ALL, but able
+ to be integrated into a general query.
+
+
+
+ ROWS_IN would typically be placed in the
+ FROM clause. Since the columns specified in the
+ REFCURSOR may not be determinable at query plan
+ time, it is required to use a column definition list. For example:
+
+
+
+SELECT cur.i, cur.t
+ FROM
+ ROWS_IN(fn(...)) AS cur(i int, t text);
+
+
+
+ The REFCURSOR must be open, and the query must be a
+ SELECT statement. If the REFCURSOR’s
+ output does not
+ correspond to that declared in the query, an error is raised at
+ query execution time. UPDATE and other DML
+ statements are not permitted, even if they return a set of rows.
+ Even if the REFCURSOR returns rows from a physical table, they may
+ not be SELECTed FOR UPDATE.
+
+
+
+ It is also possible to place ROWS_IN in the
+ SELECT list. Tools to manipulate the results from an untyped
+ RECORD are relatively few, making its use limited in that context.
+ One exception is row_to_json (see
+ ).
+
+
+
+
+ It is acceptable to use multiple ROWS_IN constructs
+ referencing multiple REFCURSORs in the same
+ query. If several references
+ are made to the same REFCURSOR, place the construction
+ inside a WITH subquery
+ marked MATERIALIZE ALWAYS.
+
+
+
+
+
+ When placed in the FROM clause,
+ ROWS_IN results are staged
+ before being processed by the executor. Do not rely upon this
+ behaviour as it may be changed in future. When placed in the
+ SELECT list, rows are always released one at a time.
+
+
+
+
+
+ When the REFCURSOR is returned from a VOLATILE
+ function, the query associated with the REFCURSOR is
+ always executed as is. When the REFCURSOR is returned
+ from a STABLE or IMMUTABLE function, in
+ future, the planner may be able to inline the REFCURSOR
+ query into the outer query.
+
+
+
+
+
System Information Functions and Operators
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 2987a55..db1a6bd 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -3427,6 +3427,16 @@ COMMIT;
+
+ Querying cursors
+
+
+ Section discusses the use of
+ ROWS_IN which allows a returned
+ REFCURSOR to be queried in a normal
+ SELECT command.
+
+
diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 790d7a2..ae5d6d3 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -81,6 +81,7 @@ OBJS = \
rangetypes_selfuncs.o \
rangetypes_spgist.o \
rangetypes_typanalyze.o \
+ refcursor.o \
regexp.o \
regproc.o \
ri_triggers.o \
diff --git a/src/backend/utils/adt/refcursor.c b/src/backend/utils/adt/refcursor.c
new file mode 100644
index 0000000..d955c2f
--- /dev/null
+++ b/src/backend/utils/adt/refcursor.c
@@ -0,0 +1,394 @@
+/*-------------------------------------------------------------------------
+ *
+ * refcursor.c
+ *
+ * IDENTIFICATION
+ * src/backend/utils/adt/refcursor.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include "optimizer/optimizer.h"
+#include "tcop/pquery.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/lsyscache.h"
+#include "utils/refcursor.h"
+#include "utils/typcache.h"
+
+
+typedef struct SingleSlotDestReceiver
+{
+ DestReceiver pub;
+ TupleTableSlot *received_slot;
+ TupleDesc tupdesc;
+} SingleSlotDestReceiver;
+
+/*
+ * sqlfunction_startup --- executor startup
+ */
+static void
+ssdr_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
+{
+ SingleSlotDestReceiver *myState = (SingleSlotDestReceiver *) self;
+
+ myState->tupdesc = typeinfo;
+}
+
+/*
+ * sqlfunction_receive --- receive one tuple
+ */
+static bool
+ssdr_receive(TupleTableSlot *slot, DestReceiver *self)
+{
+ SingleSlotDestReceiver *myState = (SingleSlotDestReceiver *) self;
+
+ myState->received_slot = slot;
+
+ return true;
+}
+
+/*
+ * sqlfunction_shutdown --- executor end
+ */
+static void
+ssdr_shutdown(DestReceiver *self)
+{
+ /* no-op */
+}
+
+/*
+ * sqlfunction_destroy --- release DestReceiver object
+ */
+static void
+ssdr_destroy(DestReceiver *self)
+{
+ pfree(self);
+}
+
+/*
+ * CreateSingleSlotDestReceiver -- create a DestReceiver
+ * that acquires a single tupleslot
+ */
+static DestReceiver *
+CreateSingleSlotDestReceiver(void)
+{
+ SingleSlotDestReceiver *self = (SingleSlotDestReceiver *) palloc(sizeof(SingleSlotDestReceiver));
+
+ self->pub.receiveSlot = ssdr_receive;
+ self->pub.rStartup = ssdr_startup;
+ self->pub.rShutdown = ssdr_shutdown;
+ self->pub.rDestroy = ssdr_destroy;
+ self->pub.mydest = -1;
+
+ /* private fields will be set by ssdr_startup */
+
+ return (DestReceiver *) self;
+}
+
+/*
+ * ROWS_IN (REFCURSOR)
+ */
+Datum
+rows_in_refcursor(PG_FUNCTION_ARGS)
+{
+ typedef struct
+ {
+ Portal portal;
+ SingleSlotDestReceiver *dest;
+ TupleDesc tupdesc;
+ } rows_in_refcursor_fctx;
+
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ FuncCallContext *funcctx;
+ rows_in_refcursor_fctx *fctx;
+ char *portal_name;
+ FetchDirection direction;
+ uint64 howMany;
+ uint64 nfetched;
+ bool first_call;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Datum datum;
+ HeapTupleHeader result;
+
+ /* stuff done only on the first call of the function */
+ first_call = SRF_IS_FIRSTCALL();
+ if (first_call)
+ {
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* Check to see if caller supports us returning a set */
+ 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_ValuePerCall))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("value per call mode required, but it is not " \
+ "allowed in this context")));
+
+ /*
+ * switch to memory context appropriate for multiple function calls
+ */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ /* allocate memory for user context */
+ fctx = (rows_in_refcursor_fctx *) palloc(sizeof(rows_in_refcursor_fctx));
+
+ fctx->dest = (SingleSlotDestReceiver *) CreateSingleSlotDestReceiver();
+
+ MemoryContextSwitchTo(oldcontext);
+
+ portal_name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+ fctx->portal = GetPortalByName(portal_name);
+
+ /* Check that the portal exists */
+ if (!PortalIsValid(fctx->portal))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_CURSOR),
+ errmsg("cursor \"%s\" does not exist", portal_name)));
+
+ /* ensure the Portal is ready (has already been OPEN'ed) */
+ if (! (fctx->portal->status == PORTAL_DEFINED ||
+ fctx->portal->status == PORTAL_READY))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor \"%s\" is not OPEN", portal_name)));
+
+ /*
+ * Ensure the Portal returns some results (so is not a utility
+ * command, or set of multiple statements.
+ */
+ if (! (fctx->portal->strategy == PORTAL_ONE_SELECT ||
+ fctx->portal->strategy == PORTAL_ONE_RETURNING ||
+ fctx->portal->strategy == PORTAL_ONE_MOD_WITH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cursor \"%s\" does not return result set", portal_name)));
+
+ pfree(portal_name);
+
+ funcctx->user_fctx = fctx;
+ }
+
+ /* stuff done on every call of the function */
+ funcctx = SRF_PERCALL_SETUP();
+ fctx = funcctx->user_fctx;
+
+ rsinfo->returnMode = SFRM_ValuePerCall;
+
+ if (first_call)
+ {
+ direction = FETCH_ABSOLUTE; /* start from absolute position */
+ howMany = 1; /* position = 1 (top); also reads one row */
+ }
+ else
+ {
+ direction = FETCH_FORWARD; /* othrewise advance forward */
+ howMany = 1; /* count = 1 (read one row) */
+ }
+
+ /* Run the cursor... */
+ nfetched = PortalRunFetch (fctx->portal, direction, howMany,
+ (DestReceiver *) fctx->dest);
+
+ /*
+ * Initialise the Tuple Desriptor. (This can't be done until
+ * we have done our first fetch.)
+ */
+ if (first_call)
+ {
+ MemoryContext per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+ MemoryContext oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+ fctx->tupdesc = CreateTupleDescCopy (fctx->dest->tupdesc);
+
+ /* For RECORD results, make sure a typmod has been assigned */
+ if (fctx->tupdesc->tdtypeid == RECORDOID &&
+ fctx->tupdesc->tdtypmod < 0)
+ assign_record_type_typmod(fctx->tupdesc);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ rsinfo->setDesc = fctx->tupdesc;
+
+ Assert (nfetched <= 1);
+
+ if (nfetched == 1)
+ {
+ /*
+ * Convert the TableTupleSlot to a HeapTuple (doesn't
+ * materialise and doesn't copy unless unavoidable).
+ */
+ tuple = ExecFetchSlotHeapTuple (fctx->dest->received_slot,
+ /* materialise */ false,
+ NULL);
+
+ /*
+ * Avoid making a copy if the HeapTuple is already
+ * fully in memory and marked with correct typeid/typmod.
+ */
+ datum = PointerGetDatum (tuple->t_data);
+ if (HeapTupleHasExternal(tuple) ||
+ HeapTupleHeaderGetTypeId(tuple->t_data) != fctx->tupdesc->tdtypeid ||
+ HeapTupleHeaderGetTypMod(tuple->t_data) != fctx->tupdesc->tdtypmod)
+ {
+ /*
+ * Copy the tuple as a Datum, ensuring it is
+ * fully in memory in the process.
+ */
+ datum = heap_copy_tuple_as_datum (tuple, fctx->tupdesc);
+ }
+
+ /*
+ * Obtain HeapTupleHeader for the Datum, which is in
+ * memory, so should not require a copy.
+ */
+ result = DatumGetHeapTupleHeader (datum);
+
+ SRF_RETURN_NEXT (funcctx, PointerGetDatum (result));
+ }
+ else /* no rows retrieved */
+ {
+ /* it will have been pfree()'ed by ssdr_destroy() */
+ fctx->dest = NULL;
+
+ /* fctx itself will be released when multi_call_memory_ctx goes. */
+
+ SRF_RETURN_DONE(funcctx);
+ }
+}
+
+
+/*
+ * Planner support function for ROWS_IN (REFCURSOR)
+ */
+Datum
+rows_in_refcursor_support(PG_FUNCTION_ARGS)
+{
+ Node *rawreq = (Node *) PG_GETARG_POINTER(0);
+ Node *ret;
+ Node *req_node;
+ SupportRequestRows *rows_req = NULL; /* keep compiler happy */
+ SupportRequestCost *cost_req = NULL; /* keep compiler happy */
+ List *args;
+ Node *arg1;
+ Const *cexpr;
+ char *portal_name;
+ Portal portal;
+ QueryDesc *qdesc;
+ PlanState *planstate;
+ Oid typoutput;
+ bool typIsVarlena;
+
+ if (IsA(rawreq, SupportRequestRows))
+ {
+ rows_req = (SupportRequestRows *) rawreq;
+
+ req_node = rows_req->node;
+ }
+ else if (IsA (rawreq, SupportRequestCost))
+ {
+ cost_req = (SupportRequestCost *) rawreq;
+
+ req_node = cost_req->node;
+ }
+ else
+ PG_RETURN_POINTER(NULL);
+
+ /* The call to ROWS_IN should be in a FuncExpr node. */
+ if (!is_funcclause(req_node))
+ PG_RETURN_POINTER(NULL);
+
+ args = ((FuncExpr *) req_node)->args;
+ if (args == NULL)
+ PG_RETURN_POINTER(NULL);
+
+ arg1 = linitial(args);
+
+ /*
+ * We can only estimate the cost if the REFCURSOR is
+ * already simplified to a Const.
+ */
+ if (!IsA (arg1, Const))
+ PG_RETURN_POINTER(NULL);
+
+ cexpr = (Const *) arg1;
+
+ if (cexpr->constisnull)
+ PG_RETURN_POINTER(NULL);
+
+ if (cexpr->consttype != REFCURSOROID)
+ PG_RETURN_POINTER(NULL);
+
+ /*
+ * We can ignore a check on the collation because we are not
+ * interested in sorting, and typemod because REFCURSOR has
+ * no modifyable attributes.
+ */
+ getTypeOutputInfo(cexpr->consttype, &typoutput, &typIsVarlena);
+
+ portal_name = OidOutputFunctionCall(typoutput, cexpr->constvalue);
+
+ portal = GetPortalByName(portal_name);
+
+ /* Check that the portal exists */
+ if (!PortalIsValid(portal))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_CURSOR),
+ errmsg("cursor \"%s\" does not exist", portal_name)));
+
+ /* ensure the Portal is ready (has already been OPEN'ed) */
+ if (! (portal->status == PORTAL_DEFINED ||
+ portal->status == PORTAL_READY))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_STATE),
+ errmsg("cursor \"%s\" is not OPEN", portal_name)));
+
+ /*
+ * Ensure the Portal returns some results (so is
+ * not a utility command, or set of multiple statements.
+ */
+ if (! (portal->strategy == PORTAL_ONE_SELECT ||
+ portal->strategy == PORTAL_ONE_RETURNING ||
+ portal->strategy == PORTAL_ONE_MOD_WITH))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
+ errmsg("cursor \"%s\" does not return result set", portal_name)));
+
+ qdesc = portal->queryDesc;
+ if (qdesc == NULL)
+ PG_RETURN_POINTER(NULL);
+
+ planstate = qdesc->planstate;
+ if (planstate == NULL)
+ PG_RETURN_POINTER(NULL);
+
+ if (rows_req)
+ {
+ rows_req->rows = planstate->plan->plan_rows;
+ }
+ else if (cost_req)
+ {
+ cost_req->startup = planstate->plan->startup_cost;
+ cost_req->per_tuple = (planstate->plan->total_cost - planstate->plan->startup_cost);
+ if (planstate->plan->plan_rows != 0.0)
+ cost_req->per_tuple /= planstate->plan->plan_rows;
+ }
+
+ ret = (Node *) rawreq;
+
+ PG_RETURN_POINTER(ret);
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a12..64fabf2 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1575,6 +1575,13 @@
{ oid => '3996', descr => 'planner support for array_unnest',
proname => 'array_unnest_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'array_unnest_support' },
+{ oid => '12921', descr => 'expand refcursor to set of rows',
+ proname => 'rows_in', prorows => '100', prosupport => 'rows_in_refcursor_support',
+ proretset => 't', prorettype => 'record', proargtypes => 'refcursor',
+ prosrc => 'rows_in_refcursor' },
+{ oid => '12923', descr => 'planner support for rows_in',
+ proname => 'rows_in_refcursor_support', prorettype => 'internal',
+ proargtypes => 'internal', prosrc => 'rows_in_refcursor_support' },
{ oid => '3167',
descr => 'remove any occurrences of an element from an array',
proname => 'array_remove', proisstrict => 'f', prorettype => 'anyarray',
diff --git a/src/include/utils/refcursor.h b/src/include/utils/refcursor.h
new file mode 100644
index 0000000..e636193
--- /dev/null
+++ b/src/include/utils/refcursor.h
@@ -0,0 +1,15 @@
+/*-------------------------------------------------------------------------
+*
+* refcursor.c
+*
+* IDENTIFICATION
+* src/include/utils/refcursor.c
+*
+*-------------------------------------------------------------------------
+*/
+
+#ifndef refcursor_h
+#define refcursor_h
+
+
+#endif /* refcursor_h */
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index cd2c79f..f8d40eb 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2078,6 +2078,358 @@ select refcursor_test2(20000, 20000) as "Should be false",
f | t
(1 row)
+-- Check ability to consume from REFCURSOR in SELECT
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int);
+ QUERY PLAN
+------------------------------------------------------
+ Function Scan on pg_catalog.rows_in
+ Output: a
+ Function Call: rows_in(return_unnamed_refcursor())
+(3 rows)
+
+select * from rows_in(return_unnamed_refcursor()) as (a int);
+ a
+-----
+ 5
+ 50
+ 500
+(3 rows)
+
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int) where a >= 50;
+ QUERY PLAN
+------------------------------------------------------
+ Function Scan on pg_catalog.rows_in
+ Output: a
+ Function Call: rows_in(return_unnamed_refcursor())
+ Filter: (rows_in.a >= 50)
+(4 rows)
+
+select * from rows_in(return_unnamed_refcursor()) as (a int) where a >= 50;
+ a
+-----
+ 50
+ 500
+(2 rows)
+
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int) order by a desc;
+ QUERY PLAN
+------------------------------------------------------------
+ Sort
+ Output: a
+ Sort Key: rows_in.a DESC
+ -> Function Scan on pg_catalog.rows_in
+ Output: a
+ Function Call: rows_in(return_unnamed_refcursor())
+(6 rows)
+
+select * from rows_in(return_unnamed_refcursor()) as (a int) order by a desc;
+ a
+-----
+ 500
+ 50
+ 5
+(3 rows)
+
+-- Check multiple scan of REFCURSOR
+explain (verbose, costs off) select * from (select 1 nr union all select 2) as scan, rows_in(return_unnamed_refcursor()) as (a int) order by nr;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Output: (1), rows_in.a
+ Sort Key: (1)
+ -> Nested Loop
+ Output: (1), rows_in.a
+ -> Function Scan on pg_catalog.rows_in
+ Output: rows_in.a
+ Function Call: rows_in(return_unnamed_refcursor())
+ -> Materialize
+ Output: (1)
+ -> Append
+ -> Result
+ Output: 1
+ -> Result
+ Output: 2
+(15 rows)
+
+select * from (select 1 nr union all select 2) as scan, rows_in(return_unnamed_refcursor()) as (a int) order by nr;
+ nr | a
+----+-----
+ 1 | 5
+ 1 | 50
+ 1 | 500
+ 2 | 5
+ 2 | 50
+ 2 | 500
+(6 rows)
+
+-- Check multiple reference to single REFCURSOR
+explain (verbose, costs off)
+ select * from
+ rows_in(return_unnamed_refcursor()) as a(n int),
+ rows_in(return_unnamed_refcursor()) as b(n int)
+ order by a.n, b.n;
+ QUERY PLAN
+------------------------------------------------------------------
+ Sort
+ Output: a.n, b.n
+ Sort Key: a.n, b.n
+ -> Nested Loop
+ Output: a.n, b.n
+ -> Function Scan on pg_catalog.rows_in a
+ Output: a.n
+ Function Call: rows_in(return_unnamed_refcursor())
+ -> Function Scan on pg_catalog.rows_in b
+ Output: b.n
+ Function Call: rows_in(return_unnamed_refcursor())
+(11 rows)
+
+select * from
+ rows_in(return_unnamed_refcursor()) as a(n int),
+ rows_in(return_unnamed_refcursor()) as b(n int)
+ order by a.n, b.n;
+ n | n
+-----+-----
+ 5 | 5
+ 5 | 50
+ 5 | 500
+ 50 | 5
+ 50 | 50
+ 50 | 500
+ 500 | 5
+ 500 | 50
+ 500 | 500
+(9 rows)
+
+explain (verbose, costs off)
+ select r.cur::text, a.n, b.n from
+ (select return_unnamed_refcursor() cur) r,
+ rows_in (r.cur) as a(n int),
+ rows_in (r.cur) as b(n int)
+ order by r.cur::text, a.n, b.n;
+ QUERY PLAN
+--------------------------------------------------------------------------
+ Sort
+ Output: (((return_unnamed_refcursor()))::text), a.n, b.n
+ Sort Key: (((return_unnamed_refcursor()))::text), a.n, b.n
+ -> Nested Loop
+ Output: ((return_unnamed_refcursor()))::text, a.n, b.n
+ -> Nested Loop
+ Output: (return_unnamed_refcursor()), a.n
+ -> Result
+ Output: return_unnamed_refcursor()
+ -> Function Scan on pg_catalog.rows_in a
+ Output: a.n
+ Function Call: rows_in((return_unnamed_refcursor()))
+ -> Function Scan on pg_catalog.rows_in b
+ Output: b.n
+ Function Call: rows_in((return_unnamed_refcursor()))
+(15 rows)
+
+select r.cur::text, a.n, b.n from
+ (select return_unnamed_refcursor() cur) r,
+ rows_in (r.cur) as a(n int),
+ rows_in (r.cur) as b(n int)
+ order by r.cur::text, a.n, b.n;
+ cur | n | n
+---------------------+-----+-----
+ | 5 | 5
+ | 5 | 50
+ | 5 | 500
+ | 50 | 5
+ | 50 | 50
+ | 50 | 500
+ | 500 | 5
+ | 500 | 50
+ | 500 | 500
+(9 rows)
+
+-- Check use of REFCURSOR in WITH
+with rcq as (
+ select * from rows_in(return_unnamed_refcursor()) as (a int)
+)
+select * from rcq where a <= 50;
+ a
+----
+ 5
+ 50
+(2 rows)
+
+-- Check attempt to UPDATE/DELETE REFCURSOR fails
+update rows_in(return_unnamed_refcursor()) set a = 2;
+ERROR: syntax error at or near "("
+LINE 1: update rows_in(return_unnamed_refcursor()) set a = 2;
+ ^
+delete from rows_in(return_unnamed_refcursor());
+ERROR: syntax error at or near "("
+LINE 1: delete from rows_in(return_unnamed_refcursor());
+ ^
+-- Check type consistency
+select * from rows_in(return_unnamed_refcursor()) as (a int, b int);
+ERROR: function return row and query-specified return row do not match
+DETAIL: Returned row contains 1 attribute, but query expects 2.
+select * from rows_in(return_unnamed_refcursor()) as (a text);
+ERROR: function return row and query-specified return row do not match
+DETAIL: Returned type integer at ordinal position 1, but query expects text.
+select * from rows_in(return_unnamed_refcursor()) as (a jsonb);
+ERROR: function return row and query-specified return row do not match
+DETAIL: Returned type integer at ordinal position 1, but query expects jsonb.
+-- Check consumption from REFCURSOR inside plpgsql FUNCTION
+create function refcursor_test3(input refcursor) returns refcursor as $$
+declare
+ rc refcursor;
+begin
+ open rc for select a, a+1 as a_plus_1 from rows_in(input) as (a int);
+ return rc;
+end
+$$ language plpgsql;
+explain (verbose, costs off) select * from rows_in(refcursor_test3(return_unnamed_refcursor())) as (a int, ap1 int) where a >= 50;
+ QUERY PLAN
+-----------------------------------------------------------------------
+ Function Scan on pg_catalog.rows_in
+ Output: a, ap1
+ Function Call: rows_in(refcursor_test3(return_unnamed_refcursor()))
+ Filter: (rows_in.a >= 50)
+(4 rows)
+
+select * from rows_in(refcursor_test3(return_unnamed_refcursor())) as (a int, ap1 int) where a >= 50;
+ a | ap1
+-----+-----
+ 50 | 51
+ 500 | 501
+(2 rows)
+
+drop function refcursor_test3;
+-- Check consumption from REFCURSOR defined with USING
+create function refcursor_test4(minimum int) returns refcursor as $$
+declare
+ rc refcursor;
+begin
+ OPEN rc FOR EXECUTE 'select a from rc_test where a >= $1'
+ USING (minimum);
+ return rc;
+end
+$$ language plpgsql;
+explain (verbose, costs off)
+ select * from rows_in(refcursor_test4(50)) as (a int);
+ QUERY PLAN
+-----------------------------------------------
+ Function Scan on pg_catalog.rows_in
+ Output: a
+ Function Call: rows_in(refcursor_test4(50))
+(3 rows)
+
+select * from rows_in(refcursor_test4(50)) as (a int);
+ a
+-----
+ 50
+ 500
+(2 rows)
+
+drop function refcursor_test4;
+-- Check consumption from REFCURSOR defined in immediate mode
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select * from rows_in('c'::refcursor) as (i int, v text);
+ i | v
+---+-----
+ 1 | one
+ 2 | two
+(2 rows)
+
+rollback;
+-- Check consumption from REFCURSOR directly in target list (tSRF)
+-- (This isn't particularly useful, but it should work.)
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select rows_in('c'::refcursor);
+ rows_in
+---------
+ (1,one)
+ (2,two)
+(2 rows)
+
+rollback;
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select rows_in('c'::refcursor), * from rows_in('c'::refcursor) as (i int, v text);
+ rows_in | i | v
+---------+---+-----
+ (1,one) | 1 | one
+ (2,two) | 1 | one
+ (1,one) | 2 | two
+ (2,two) | 2 | two
+(4 rows)
+
+rollback;
+-- Check use of ROWS_IN with row_to_json both in the target list
+-- and in the FROM.
+begin;
+declare c cursor for
+ with recursive t as (
+ select 1 i
+ union all
+ select i + 1
+ from t
+ where i + 1 <= 10
+ )
+ select i, 'number ' || i t from t;
+select row_to_json(rows_in('c'::refcursor));
+ row_to_json
+--------------------------
+ {"i":1,"t":"number 1"}
+ {"i":2,"t":"number 2"}
+ {"i":3,"t":"number 3"}
+ {"i":4,"t":"number 4"}
+ {"i":5,"t":"number 5"}
+ {"i":6,"t":"number 6"}
+ {"i":7,"t":"number 7"}
+ {"i":8,"t":"number 8"}
+ {"i":9,"t":"number 9"}
+ {"i":10,"t":"number 10"}
+(10 rows)
+
+rollback;
+begin;
+declare c cursor for
+ with recursive t as (
+ select 1 i
+ union all
+ select i + 1
+ from t
+ where i + 1 <= 1000
+ )
+ select i, 'number ' || i t from t;
+select j.r,
+ (j.r->'i')::int i, (j.r->'t')::text t
+ from (select row_to_json(rows_in('c'::refcursor))::jsonb as r) j
+ where
+ (j.r->'i')::int >= 50
+ and (j.r->'i')::int <= 60;
+ r | i | t
+-----------------------------+----+-------------
+ {"i": 50, "t": "number 50"} | 50 | "number 50"
+ {"i": 51, "t": "number 51"} | 51 | "number 51"
+ {"i": 52, "t": "number 52"} | 52 | "number 52"
+ {"i": 53, "t": "number 53"} | 53 | "number 53"
+ {"i": 54, "t": "number 54"} | 54 | "number 54"
+ {"i": 55, "t": "number 55"} | 55 | "number 55"
+ {"i": 56, "t": "number 56"} | 56 | "number 56"
+ {"i": 57, "t": "number 57"} | 57 | "number 57"
+ {"i": 58, "t": "number 58"} | 58 | "number 58"
+ {"i": 59, "t": "number 59"} | 59 | "number 59"
+ {"i": 60, "t": "number 60"} | 60 | "number 60"
+(11 rows)
+
+rollback;
--
-- tests for cursors with named parameter arguments
--
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index d841d8c..2fc76a9 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -1805,6 +1805,143 @@ $$ language plpgsql;
select refcursor_test2(20000, 20000) as "Should be false",
refcursor_test2(20, 20) as "Should be true";
+-- Check ability to consume from REFCURSOR in SELECT
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int);
+select * from rows_in(return_unnamed_refcursor()) as (a int);
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int) where a >= 50;
+select * from rows_in(return_unnamed_refcursor()) as (a int) where a >= 50;
+explain (verbose, costs off) select * from rows_in(return_unnamed_refcursor()) as (a int) order by a desc;
+select * from rows_in(return_unnamed_refcursor()) as (a int) order by a desc;
+
+-- Check multiple scan of REFCURSOR
+explain (verbose, costs off) select * from (select 1 nr union all select 2) as scan, rows_in(return_unnamed_refcursor()) as (a int) order by nr;
+select * from (select 1 nr union all select 2) as scan, rows_in(return_unnamed_refcursor()) as (a int) order by nr;
+
+-- Check multiple reference to single REFCURSOR
+explain (verbose, costs off)
+ select * from
+ rows_in(return_unnamed_refcursor()) as a(n int),
+ rows_in(return_unnamed_refcursor()) as b(n int)
+ order by a.n, b.n;
+select * from
+ rows_in(return_unnamed_refcursor()) as a(n int),
+ rows_in(return_unnamed_refcursor()) as b(n int)
+ order by a.n, b.n;
+explain (verbose, costs off)
+ select r.cur::text, a.n, b.n from
+ (select return_unnamed_refcursor() cur) r,
+ rows_in (r.cur) as a(n int),
+ rows_in (r.cur) as b(n int)
+ order by r.cur::text, a.n, b.n;
+select r.cur::text, a.n, b.n from
+ (select return_unnamed_refcursor() cur) r,
+ rows_in (r.cur) as a(n int),
+ rows_in (r.cur) as b(n int)
+ order by r.cur::text, a.n, b.n;
+
+-- Check use of REFCURSOR in WITH
+with rcq as (
+ select * from rows_in(return_unnamed_refcursor()) as (a int)
+)
+select * from rcq where a <= 50;
+
+-- Check attempt to UPDATE/DELETE REFCURSOR fails
+update rows_in(return_unnamed_refcursor()) set a = 2;
+delete from rows_in(return_unnamed_refcursor());
+
+-- Check type consistency
+select * from rows_in(return_unnamed_refcursor()) as (a int, b int);
+select * from rows_in(return_unnamed_refcursor()) as (a text);
+select * from rows_in(return_unnamed_refcursor()) as (a jsonb);
+
+-- Check consumption from REFCURSOR inside plpgsql FUNCTION
+create function refcursor_test3(input refcursor) returns refcursor as $$
+declare
+ rc refcursor;
+begin
+ open rc for select a, a+1 as a_plus_1 from rows_in(input) as (a int);
+ return rc;
+end
+$$ language plpgsql;
+explain (verbose, costs off) select * from rows_in(refcursor_test3(return_unnamed_refcursor())) as (a int, ap1 int) where a >= 50;
+select * from rows_in(refcursor_test3(return_unnamed_refcursor())) as (a int, ap1 int) where a >= 50;
+
+drop function refcursor_test3;
+
+-- Check consumption from REFCURSOR defined with USING
+create function refcursor_test4(minimum int) returns refcursor as $$
+declare
+ rc refcursor;
+begin
+ OPEN rc FOR EXECUTE 'select a from rc_test where a >= $1'
+ USING (minimum);
+ return rc;
+end
+$$ language plpgsql;
+explain (verbose, costs off)
+ select * from rows_in(refcursor_test4(50)) as (a int);
+select * from rows_in(refcursor_test4(50)) as (a int);
+
+drop function refcursor_test4;
+
+-- Check consumption from REFCURSOR defined in immediate mode
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select * from rows_in('c'::refcursor) as (i int, v text);
+rollback;
+
+-- Check consumption from REFCURSOR directly in target list (tSRF)
+-- (This isn't particularly useful, but it should work.)
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select rows_in('c'::refcursor);
+rollback;
+begin;
+declare c cursor for
+ select 1 as i, 'one' v
+ union all
+ select 2, 'two';
+select rows_in('c'::refcursor), * from rows_in('c'::refcursor) as (i int, v text);
+rollback;
+
+-- Check use of ROWS_IN with row_to_json both in the target list
+-- and in the FROM.
+begin;
+declare c cursor for
+ with recursive t as (
+ select 1 i
+ union all
+ select i + 1
+ from t
+ where i + 1 <= 10
+ )
+ select i, 'number ' || i t from t;
+select row_to_json(rows_in('c'::refcursor));
+rollback;
+begin;
+declare c cursor for
+ with recursive t as (
+ select 1 i
+ union all
+ select i + 1
+ from t
+ where i + 1 <= 1000
+ )
+ select i, 'number ' || i t from t;
+select j.r,
+ (j.r->'i')::int i, (j.r->'t')::text t
+ from (select row_to_json(rows_in('c'::refcursor))::jsonb as r) j
+ where
+ (j.r->'i')::int >= 50
+ and (j.r->'i')::int <= 60;
+rollback;
+
--
-- tests for cursors with named parameter arguments
--