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 +---------------------+-----+----- + | 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 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 --