From a9545b3aef0d41fdb84bc6a30fa2e563020acad2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Mon, 11 Jun 2007 22:22:42 +0000 Subject: [PATCH] Improve UPDATE/DELETE WHERE CURRENT OF so that they can be used from plpgsql with a plpgsql-defined cursor. The underlying mechanism for this is that the main SQL engine will now take "WHERE CURRENT OF $n" where $n is a refcursor parameter. Not sure if we should document that fact or consider it an implementation detail. Per discussion with Pavel Stehule. --- doc/src/sgml/plpgsql.sgml | 27 ++++++- src/backend/executor/execCurrent.c | 74 ++++++++++++++++--- src/backend/executor/execQual.c | 27 ++++--- src/backend/executor/nodeTidscan.c | 4 +- src/backend/nodes/copyfuncs.c | 3 +- src/backend/nodes/equalfuncs.c | 3 +- src/backend/nodes/outfuncs.c | 3 +- src/backend/nodes/readfuncs.c | 3 +- src/backend/parser/gram.y | 12 ++- src/backend/parser/parse_expr.c | 114 +++++++++++++++++++---------- src/backend/utils/adt/ruleutils.c | 14 +++- src/include/executor/executor.h | 6 +- src/include/nodes/primnodes.h | 9 ++- 13 files changed, 226 insertions(+), 73 deletions(-) diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 855b05c0ef..18ba19dd77 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,4 +1,4 @@ - + <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language @@ -2614,6 +2614,31 @@ MOVE direction { FROM | IN } < MOVE curs1; MOVE LAST FROM curs3; MOVE RELATIVE -2 FROM curs4; + + + + + + <literal>UPDATE/DELETE WHERE CURRENT OF</> + + +UPDATE table SET ... WHERE CURRENT OF cursor; +DELETE FROM table WHERE CURRENT OF cursor; + + + + When a cursor is positioned on a table row, that row can be updated + or deleted using the cursor to identify the row. Note that this + only works for simple (non-join, non-grouping) cursor queries. + For additional information see the + + reference page. + + + + An example: + +UPDATE foo SET dataval = myval WHERE CURRENT OF curs1; diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c index ce95d58b81..72bccd4438 100644 --- a/src/backend/executor/execCurrent.c +++ b/src/backend/executor/execCurrent.c @@ -6,26 +6,29 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.1 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execCurrent.c,v 1.2 2007/06/11 22:22:40 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" +#include "catalog/pg_type.h" #include "executor/executor.h" +#include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/portal.h" +static char *fetch_param_value(ExprContext *econtext, int paramId); static ScanState *search_plan_tree(PlanState *node, Oid table_oid); /* * execCurrentOf * - * Given the name of a cursor and the OID of a table, determine which row - * of the table is currently being scanned by the cursor, and return its - * TID into *current_tid. + * Given a CURRENT OF expression and the OID of a table, determine which row + * of the table is currently being scanned by the cursor named by CURRENT OF, + * and return the row's TID into *current_tid. * * Returns TRUE if a row was identified. Returns FALSE if the cursor is valid * for the table but is not currently scanning a row of the table (this is a @@ -33,14 +36,25 @@ static ScanState *search_plan_tree(PlanState *node, Oid table_oid); * valid updatable scan of the specified table. */ bool -execCurrentOf(char *cursor_name, Oid table_oid, +execCurrentOf(CurrentOfExpr *cexpr, + ExprContext *econtext, + Oid table_oid, ItemPointer current_tid) { + char *cursor_name; char *table_name; Portal portal; QueryDesc *queryDesc; ScanState *scanstate; - HeapTuple tup; + bool lisnull; + Oid tuple_tableoid; + ItemPointer tuple_tid; + + /* Get the cursor name --- may have to look up a parameter reference */ + if (cexpr->cursor_name) + cursor_name = cexpr->cursor_name; + else + cursor_name = fetch_param_value(econtext, cexpr->cursor_param); /* Fetch table name for possible use in error messages */ table_name = get_rel_name(table_oid); @@ -100,16 +114,54 @@ execCurrentOf(char *cursor_name, Oid table_oid, if (TupIsNull(scanstate->ss_ScanTupleSlot)) return false; - tup = scanstate->ss_ScanTupleSlot->tts_tuple; - if (tup == NULL) - elog(ERROR, "CURRENT OF applied to non-materialized tuple"); - Assert(tup->t_tableOid == table_oid); + /* Use slot_getattr to catch any possible mistakes */ + tuple_tableoid = DatumGetObjectId(slot_getattr(scanstate->ss_ScanTupleSlot, + TableOidAttributeNumber, + &lisnull)); + Assert(!lisnull); + tuple_tid = (ItemPointer) + DatumGetPointer(slot_getattr(scanstate->ss_ScanTupleSlot, + SelfItemPointerAttributeNumber, + &lisnull)); + Assert(!lisnull); + + Assert(tuple_tableoid == table_oid); - *current_tid = tup->t_self; + *current_tid = *tuple_tid; return true; } +/* + * fetch_param_value + * + * Fetch the string value of a param, verifying it is of type REFCURSOR. + */ +static char * +fetch_param_value(ExprContext *econtext, int paramId) +{ + ParamListInfo paramInfo = econtext->ecxt_param_list_info; + + if (paramInfo && + paramId > 0 && paramId <= paramInfo->numParams) + { + ParamExternData *prm = ¶mInfo->params[paramId - 1]; + + if (OidIsValid(prm->ptype) && !prm->isnull) + { + Assert(prm->ptype == REFCURSOROID); + /* We know that refcursor uses text's I/O routines */ + return DatumGetCString(DirectFunctionCall1(textout, + prm->value)); + } + } + + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no value found for parameter %d", paramId))); + return NULL; +} + /* * search_plan_tree * diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c index 5549142e70..69d28e78a4 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.219 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/execQual.c,v 1.220 2007/06/11 22:22:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -3632,8 +3632,10 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, bool *isNull, ExprDoneCond *isDone) { CurrentOfExpr *cexpr = (CurrentOfExpr *) exprstate->expr; - bool result; - HeapTuple tup; + bool result; + bool lisnull; + Oid tableoid; + ItemPointer tuple_tid; ItemPointerData cursor_tid; if (isDone) @@ -3643,12 +3645,19 @@ ExecEvalCurrentOfExpr(ExprState *exprstate, ExprContext *econtext, Assert(cexpr->cvarno != INNER); Assert(cexpr->cvarno != OUTER); Assert(!TupIsNull(econtext->ecxt_scantuple)); - tup = econtext->ecxt_scantuple->tts_tuple; - if (tup == NULL) - elog(ERROR, "CURRENT OF applied to non-materialized tuple"); - - if (execCurrentOf(cexpr->cursor_name, tup->t_tableOid, &cursor_tid)) - result = ItemPointerEquals(&cursor_tid, &(tup->t_self)); + /* Use slot_getattr to catch any possible mistakes */ + tableoid = DatumGetObjectId(slot_getattr(econtext->ecxt_scantuple, + TableOidAttributeNumber, + &lisnull)); + Assert(!lisnull); + tuple_tid = (ItemPointer) + DatumGetPointer(slot_getattr(econtext->ecxt_scantuple, + SelfItemPointerAttributeNumber, + &lisnull)); + Assert(!lisnull); + + if (execCurrentOf(cexpr, econtext, tableoid, &cursor_tid)) + result = ItemPointerEquals(&cursor_tid, tuple_tid); else result = false; diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c index 986ff1f3f1..31a9828b6a 100644 --- a/src/backend/executor/nodeTidscan.c +++ b/src/backend/executor/nodeTidscan.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.54 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeTidscan.c,v 1.55 2007/06/11 22:22:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -153,7 +153,7 @@ TidListCreate(TidScanState *tidstate) CurrentOfExpr *cexpr = (CurrentOfExpr *) expr; ItemPointerData cursor_tid; - if (execCurrentOf(cexpr->cursor_name, + if (execCurrentOf(cexpr, econtext, RelationGetRelid(tidstate->ss.ss_currentRelation), &cursor_tid)) { diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index c868ff574d..ec3d616889 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.378 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.379 2007/06/11 22:22:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1309,6 +1309,7 @@ _copyCurrentOfExpr(CurrentOfExpr *from) COPY_SCALAR_FIELD(cvarno); COPY_STRING_FIELD(cursor_name); + COPY_SCALAR_FIELD(cursor_param); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 04072c7a65..114550f17d 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.309 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.310 2007/06/11 22:22:40 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -603,6 +603,7 @@ _equalCurrentOfExpr(CurrentOfExpr *a, CurrentOfExpr *b) { COMPARE_SCALAR_FIELD(cvarno); COMPARE_STRING_FIELD(cursor_name); + COMPARE_SCALAR_FIELD(cursor_param); return true; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 869905f0cc..7fe1503798 100644 --- a/src/backend/nodes/outfuncs.c +++ b/src/backend/nodes/outfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.310 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/outfuncs.c,v 1.311 2007/06/11 22:22:40 tgl Exp $ * * NOTES * Every node type that can appear in stored rules' parsetrees *must* @@ -1065,6 +1065,7 @@ _outCurrentOfExpr(StringInfo str, CurrentOfExpr *node) WRITE_UINT_FIELD(cvarno); WRITE_STRING_FIELD(cursor_name); + WRITE_INT_FIELD(cursor_param); } static void diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c index e91a6e5b50..cb87011b39 100644 --- a/src/backend/nodes/readfuncs.c +++ b/src/backend/nodes/readfuncs.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.208 2007/06/11 01:16:22 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/readfuncs.c,v 1.209 2007/06/11 22:22:41 tgl Exp $ * * NOTES * Path and Plan nodes do not have any readfuncs support, because we @@ -883,6 +883,7 @@ _readCurrentOfExpr(void) READ_UINT_FIELD(cvarno); READ_STRING_FIELD(cursor_name); + READ_INT_FIELD(cursor_param); READ_DONE(); } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b50be6bd73..da3afda8fb 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.592 2007/06/11 01:16:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.593 2007/06/11 22:22:41 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -6568,7 +6568,17 @@ where_or_current_clause: | WHERE CURRENT_P OF name { CurrentOfExpr *n = makeNode(CurrentOfExpr); + /* cvarno is filled in by parse analysis */ n->cursor_name = $4; + n->cursor_param = 0; + $$ = (Node *) n; + } + | WHERE CURRENT_P OF PARAM + { + CurrentOfExpr *n = makeNode(CurrentOfExpr); + /* cvarno is filled in by parse analysis */ + n->cursor_name = NULL; + n->cursor_param = $4; $$ = (Node *) n; } | /*EMPTY*/ { $$ = NULL; } diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 6601bfe40e..da4bcf208f 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.219 2007/06/11 01:16:25 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/parse_expr.c,v 1.220 2007/06/11 22:22:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,10 +59,10 @@ static Node *transformMinMaxExpr(ParseState *pstate, MinMaxExpr *m); static Node *transformXmlExpr(ParseState *pstate, XmlExpr *x); static Node *transformXmlSerialize(ParseState *pstate, XmlSerialize *xs); static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); +static Node *transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr); static Node *transformColumnRef(ParseState *pstate, ColumnRef *cref); static Node *transformWholeRowRef(ParseState *pstate, char *schemaname, char *relname, int location); -static Node *transformBooleanTest(ParseState *pstate, BooleanTest *b); static Node *transformIndirection(ParseState *pstate, Node *basenode, List *indirection); static Node *typecast_expression(ParseState *pstate, Node *expr, @@ -244,19 +244,8 @@ transformExpr(ParseState *pstate, Node *expr) break; case T_CurrentOfExpr: - { - CurrentOfExpr *c = (CurrentOfExpr *) expr; - int sublevels_up; - - /* CURRENT OF can only appear at top level of UPDATE/DELETE */ - Assert(pstate->p_target_rangetblentry != NULL); - c->cvarno = RTERangeTablePosn(pstate, - pstate->p_target_rangetblentry, - &sublevels_up); - Assert(sublevels_up == 0); - result = expr; - break; - } + result = transformCurrentOfExpr(pstate, (CurrentOfExpr *) expr); + break; /********************************************* * Quietly accept node types that may be presented when we are @@ -549,57 +538,69 @@ transformColumnRef(ParseState *pstate, ColumnRef *cref) return node; } -static Node * -transformParamRef(ParseState *pstate, ParamRef *pref) +/* + * Locate the parameter type info for the given parameter number, and + * return a pointer to it. + */ +static Oid * +find_param_type(ParseState *pstate, int paramno) { - int paramno = pref->number; - ParseState *toppstate; - Param *param; + Oid *result; /* * Find topmost ParseState, which is where paramtype info lives. */ - toppstate = pstate; - while (toppstate->parentParseState != NULL) - toppstate = toppstate->parentParseState; + while (pstate->parentParseState != NULL) + pstate = pstate->parentParseState; /* Check parameter number is in range */ if (paramno <= 0) /* probably can't happen? */ ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno))); - if (paramno > toppstate->p_numparams) + if (paramno > pstate->p_numparams) { - if (!toppstate->p_variableparams) + if (!pstate->p_variableparams) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PARAMETER), errmsg("there is no parameter $%d", paramno))); /* Okay to enlarge param array */ - if (toppstate->p_paramtypes) - toppstate->p_paramtypes = - (Oid *) repalloc(toppstate->p_paramtypes, - paramno * sizeof(Oid)); + if (pstate->p_paramtypes) + pstate->p_paramtypes = (Oid *) repalloc(pstate->p_paramtypes, + paramno * sizeof(Oid)); else - toppstate->p_paramtypes = - (Oid *) palloc(paramno * sizeof(Oid)); + pstate->p_paramtypes = (Oid *) palloc(paramno * sizeof(Oid)); /* Zero out the previously-unreferenced slots */ - MemSet(toppstate->p_paramtypes + toppstate->p_numparams, + MemSet(pstate->p_paramtypes + pstate->p_numparams, 0, - (paramno - toppstate->p_numparams) * sizeof(Oid)); - toppstate->p_numparams = paramno; + (paramno - pstate->p_numparams) * sizeof(Oid)); + pstate->p_numparams = paramno; } - if (toppstate->p_variableparams) + + result = &pstate->p_paramtypes[paramno - 1]; + + if (pstate->p_variableparams) { /* If not seen before, initialize to UNKNOWN type */ - if (toppstate->p_paramtypes[paramno - 1] == InvalidOid) - toppstate->p_paramtypes[paramno - 1] = UNKNOWNOID; + if (*result == InvalidOid) + *result = UNKNOWNOID; } + return result; +} + +static Node * +transformParamRef(ParseState *pstate, ParamRef *pref) +{ + int paramno = pref->number; + Oid *pptype = find_param_type(pstate, paramno); + Param *param; + param = makeNode(Param); param->paramkind = PARAM_EXTERN; param->paramid = paramno; - param->paramtype = toppstate->p_paramtypes[paramno - 1]; + param->paramtype = *pptype; param->paramtypmod = -1; return (Node *) param; @@ -1596,6 +1597,43 @@ transformBooleanTest(ParseState *pstate, BooleanTest *b) return (Node *) b; } +static Node * +transformCurrentOfExpr(ParseState *pstate, CurrentOfExpr *cexpr) +{ + int sublevels_up; + + /* CURRENT OF can only appear at top level of UPDATE/DELETE */ + Assert(pstate->p_target_rangetblentry != NULL); + cexpr->cvarno = RTERangeTablePosn(pstate, + pstate->p_target_rangetblentry, + &sublevels_up); + Assert(sublevels_up == 0); + + /* If a parameter is used, it must be of type REFCURSOR */ + if (cexpr->cursor_name == NULL) + { + Oid *pptype = find_param_type(pstate, cexpr->cursor_param); + + if (pstate->p_variableparams && *pptype == UNKNOWNOID) + { + /* resolve unknown param type as REFCURSOR */ + *pptype = REFCURSOROID; + } + else if (*pptype != REFCURSOROID) + { + ereport(ERROR, + (errcode(ERRCODE_AMBIGUOUS_PARAMETER), + errmsg("inconsistent types deduced for parameter $%d", + cexpr->cursor_param), + errdetail("%s versus %s", + format_type_be(*pptype), + format_type_be(REFCURSOROID)))); + } + } + + return (Node *) cexpr; +} + /* * Construct a whole-row reference to represent the notation "relation.*". * diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index c6f6b88248..49c7821cef 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.260 2007/06/11 01:16:29 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.261 2007/06/11 22:22:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -4136,8 +4136,16 @@ get_rule_expr(Node *node, deparse_context *context, break; case T_CurrentOfExpr: - appendStringInfo(buf, "CURRENT OF %s", - quote_identifier(((CurrentOfExpr *) node)->cursor_name)); + { + CurrentOfExpr *cexpr = (CurrentOfExpr *) node; + + if (cexpr->cursor_name) + appendStringInfo(buf, "CURRENT OF %s", + quote_identifier(cexpr->cursor_name)); + else + appendStringInfo(buf, "CURRENT OF $%d", + cexpr->cursor_param); + } break; case T_List: diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 408519c1e3..539e2f6fca 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.140 2007/06/11 01:16:30 tgl Exp $ + * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.141 2007/06/11 22:22:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -73,7 +73,9 @@ extern bool ExecMayReturnRawTuples(PlanState *node); /* * prototypes from functions in execCurrent.c */ -extern bool execCurrentOf(char *cursor_name, Oid table_oid, +extern bool execCurrentOf(CurrentOfExpr *cexpr, + ExprContext *econtext, + Oid table_oid, ItemPointer current_tid); /* diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 9a3e09b77e..cdcd4d5caa 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -10,7 +10,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.131 2007/06/11 01:16:30 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/primnodes.h,v 1.132 2007/06/11 22:22:42 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -922,12 +922,17 @@ typedef struct SetToDefault * of the target relation being constrained; this aids placing the expression * correctly during planning. We can assume however that its "levelsup" is * always zero, due to the syntactic constraints on where it can appear. + * + * The referenced cursor can be represented either as a hardwired string + * or as a reference to a run-time parameter of type REFCURSOR. The latter + * case is for the convenience of plpgsql. */ typedef struct CurrentOfExpr { Expr xpr; Index cvarno; /* RT index of target relation */ - char *cursor_name; /* name of referenced cursor */ + char *cursor_name; /* name of referenced cursor, or NULL */ + int cursor_param; /* refcursor parameter number, or 0 */ } CurrentOfExpr; /*-------------------- -- 2.40.0