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);
 
  </sect1>
 
+ <sect1 id="functions-cursor">
+  <title>Cursor Manipulation Functions</title>
+
+  <para>
+   This section describes functions that cursors to be manipulated
+   in normal <command>SELECT</command> queries.
+  </para>
+
+  <para>
+   The creation of cursors using <xref linkend="sql-declare"/>
+   or within a procedural language such as
+   <application>PL/pgSQL</application>
+   (see <xref linkend="plpgsql-cursor-declarations"/>), and other
+   manipulaton outside of a <command>SELECT</command> query is
+   described elsewhere in this manual.
+  </para>
+
+  <sect2>
+   <title><literal><function>ROWS_IN(<parameter>cursor</parameter>)</function></literal></title>
+
+  <indexterm zone="functions-cursor">
+   <primary>cursor manipulation functions</primary>
+   <secondary>functions</secondary>
+  </indexterm>
+
+  <indexterm>
+   <primary>rows_in</primary>
+  </indexterm>
+
+<synopsis>
+<function>ROWS_IN(<parameter>cursor</parameter> <type>REFCURSOR</type>)
+  RETURNS <type>SETOF RECORD</type></function>
+</synopsis>
+
+  <para>
+   <function>ROWS_IN</function> retrieves rows from a previously-created
+   cursor, or a <type>REFCURSOR</type> returned by a function. It may be
+   considered as equivalent to <command>FETCH ALL</command>, but able
+   to be integrated into a general query.
+  </para>
+
+  <para>
+   <function>ROWS_IN</function> would typically be placed in the
+   FROM clause. Since the columns specified in the
+   <type>REFCURSOR</type> may not be determinable at query plan
+   time, it is required to use a column definition list. For example:
+  </para>
+
+<programlisting>
+SELECT cur.i, cur.t
+  FROM 
+    ROWS_IN(fn(...)) AS cur(i int, t text);
+</programlisting>
+
+  <para>
+   The <type>REFCURSOR</type> must be open, and the query must be a
+   <command>SELECT</command> statement. If the <type>REFCURSOR</type>’s
+   output does not
+   correspond to that declared in the query, an error is raised at
+   query execution time. <command>UPDATE</command> and other DML
+   statements are not permitted, even if they return a set of rows.
+   Even if the <type>REFCURSOR</type> returns rows from a physical table, they may
+   not be <command>SELECT</command>ed <literal>FOR UPDATE</literal>.
+  </para>
+
+  <para>
+   It is also possible to place <function>ROWS_IN</function> in the
+   <command>SELECT</command> list. Tools to manipulate the results from an untyped
+   <type>RECORD</type> are relatively few, making its use limited in that context.
+   One exception is <function>row_to_json</function> (see
+   <xref linkend="functions-json-creation-table"/>).
+  </para>
+
+  <note>
+   <para>
+    It is acceptable to use multiple <function>ROWS_IN</function> constructs
+    referencing multiple <type>REFCURSOR</type>s in the same
+    query. If several references
+    are made to the same <type>REFCURSOR</type>, place the construction
+    inside a <literal>WITH</literal> subquery
+    marked <literal>MATERIALIZE ALWAYS</literal>.
+   </para>
+  </note>
+
+  <note>
+   <para>
+    When placed in the <literal>FROM</literal> clause,
+    <function>ROWS_IN</function> 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
+    <command>SELECT</command> list, rows are always released one at a time.
+   </para>
+  </note>
+
+  <note>
+   <para>
+    When the <type>REFCURSOR</type> is returned from a <literal>VOLATILE</literal>
+    function, the query associated with the <type>REFCURSOR</type> is
+    always executed as is. When the <type>REFCURSOR</type> is returned
+    from a <literal>STABLE</literal> or <literal>IMMUTABLE</literal> function, in
+    future, the planner may be able to inline the <type>REFCURSOR</type>
+    query into the outer query.
+   </para>
+  </note>
+  </sect2>
+ </sect1>
+
  <sect1 id="functions-info">
   <title>System Information Functions and Operators</title>
 
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;
 </programlisting>
        </para>
      </sect3>
+     <sect3>
+      <title>Querying cursors</title>
+
+      <para>
+       Section <xref linkend="functions-cursor"/> discusses the use of
+       <function>ROWS_IN</function> which allows a returned
+       <type>REFCURSOR</type> to be queried in a normal
+       <command>SELECT</command> command.
+      </para>
+     </sect3>
    </sect2>
 
    <sect2 id="plpgsql-cursor-for-loop">
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..068f235
--- /dev/null
+++ b/src/backend/utils/adt/refcursor.c
@@ -0,0 +1,393 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+	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);
+
+		char *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  
+---------------------+-----+-----
+ <unnamed portal 11> |   5 |   5
+ <unnamed portal 11> |   5 |  50
+ <unnamed portal 11> |   5 | 500
+ <unnamed portal 11> |  50 |   5
+ <unnamed portal 11> |  50 |  50
+ <unnamed portal 11> |  50 | 500
+ <unnamed portal 11> | 500 |   5
+ <unnamed portal 11> | 500 |  50
+ <unnamed portal 11> | 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
 --
