diff --git a/src/backend/utils/adt/Makefile b/src/backend/utils/adt/Makefile
index 5800432..7c71ed4 100644
--- a/src/backend/utils/adt/Makefile
+++ b/src/backend/utils/adt/Makefile
@@ -25,7 +25,7 @@ OBJS = acl.o amutils.o arrayfuncs.o array_expanded.o array_selfuncs.o \
 	pg_upgrade_support.o pgstatfuncs.o \
 	pseudotypes.o quote.o rangetypes.o rangetypes_gist.o \
 	rangetypes_selfuncs.o rangetypes_spgist.o rangetypes_typanalyze.o \
-	regexp.o regproc.o ri_triggers.o rowtypes.o ruleutils.o \
+	refcursor.o regexp.o regproc.o ri_triggers.o rowtypes.o ruleutils.o \
 	selfuncs.o tid.o timestamp.o trigfuncs.o \
 	tsginidx.o tsgistidx.o tsquery.o tsquery_cleanup.o tsquery_gist.o \
 	tsquery_op.o tsquery_rewrite.o tsquery_util.o tsrank.o \
diff --git a/src/backend/utils/adt/refcursor.c b/src/backend/utils/adt/refcursor.c
new file mode 100644
index 0000000..e9b7c66
--- /dev/null
+++ b/src/backend/utils/adt/refcursor.c
@@ -0,0 +1,386 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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;
+
+	/* stuff done only on the first call of the function */
+	bool 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
+		 */
+		MemoryContext 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).
+		 */
+		HeapTuple 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 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.
+		 */
+		HeapTupleHeader 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 = NULL;
+
+	if (!IsA(rawreq, SupportRequestRows)
+		&& !IsA (rawreq, SupportRequestCost))
+		PG_RETURN_POINTER(NULL);
+
+	Node *req_node = NULL;
+	if (IsA(rawreq, SupportRequestRows))
+	{
+		SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+		req_node = req->node;
+	}
+	else if (IsA (rawreq, SupportRequestCost))
+	{
+		SupportRequestCost *req = (SupportRequestCost *) rawreq;
+
+		req_node = req->node;
+	}
+
+	/* The call to ROWS_IN should be in a FuncExpr node. */
+	if (!is_funcclause(req_node))
+		PG_RETURN_POINTER(NULL);
+
+	List	   *args = ((FuncExpr *) req_node)->args;
+	if (args == NULL)
+		PG_RETURN_POINTER(NULL);
+
+	Node	   *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);
+
+	Const *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.
+	 */
+	Oid  typoutput;
+	bool typIsVarlena;
+	getTypeOutputInfo(cexpr->consttype, &typoutput, &typIsVarlena);
+
+	char *portal_name = OidOutputFunctionCall(typoutput, cexpr->constvalue);
+
+	Portal 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)));
+
+	QueryDesc *qdesc = portal->queryDesc;
+	if (qdesc == NULL)
+		PG_RETURN_POINTER(NULL);
+
+	PlanState  *planstate = qdesc->planstate;
+	if (planstate == NULL)
+		PG_RETURN_POINTER(NULL);
+
+	if (IsA(rawreq, SupportRequestRows))
+	{
+		SupportRequestRows *req = (SupportRequestRows *) rawreq;
+
+		req->rows = planstate->plan->plan_rows;
+	}
+	else if (IsA (rawreq, SupportRequestCost))
+	{
+		SupportRequestCost *req = (SupportRequestCost *) rawreq;
+
+		req->startup = planstate->plan->startup_cost;
+		req->per_tuple = (planstate->plan->total_cost - planstate->plan->startup_cost);
+		if (planstate->plan->plan_rows != 0.0)
+			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 d352f9a..fa30b8c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -1550,6 +1550,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 e85b294..9ef14a0 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -2078,6 +2078,298 @@ 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 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;
 --
 -- tests for cursors with named parameter arguments
 --
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 70deadf..eb77410 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -1805,6 +1805,111 @@ $$ 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 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;
+
 --
 -- tests for cursors with named parameter arguments
 --
