diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c index f7b26cdd974e55573f20f7e50c2e7fcadeb42787..64b29906d9c175e12effcaef4f281ea8a510f22f 100644 --- a/src/backend/utils/mmgr/portalmem.c +++ b/src/backend/utils/mmgr/portalmem.c @@ -12,7 +12,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.118 2010/02/26 02:01:14 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.119 2010/07/05 09:27:17 heikki Exp $ * *------------------------------------------------------------------------- */ @@ -376,6 +376,28 @@ PortalCreateHoldStore(Portal portal) MemoryContextSwitchTo(oldcxt); } +/* + * PinPortal + * Protect a portal from dropping. + */ +void +PinPortal(Portal portal) +{ + if (portal->portalPinned) + elog(ERROR, "portal already pinned"); + + portal->portalPinned = true; +} + +void +UnpinPortal(Portal portal) +{ + if (!portal->portalPinned) + elog(ERROR, "portal not pinned"); + + portal->portalPinned = false; +} + /* * PortalDrop * Destroy the portal. @@ -385,9 +407,16 @@ PortalDrop(Portal portal, bool isTopCommit) { AssertArg(PortalIsValid(portal)); - /* Not sure if this case can validly happen or not... */ - if (portal->status == PORTAL_ACTIVE) - elog(ERROR, "cannot drop active portal"); + /* + * Don't allow dropping a pinned portal, it's still needed by whoever + * pinned it. Not sure if the PORTAL_ACTIVE case can validly happen or + * not... + */ + if (portal->portalPinned || + portal->status == PORTAL_ACTIVE) + ereport(ERROR, + (errcode(ERRCODE_INVALID_CURSOR_STATE), + errmsg("cannot drop active portal \"%s\"", portal->name))); /* * Remove portal from hash table. Because we do this first, we will not @@ -630,6 +659,13 @@ AtCommit_Portals(void) continue; } + /* + * There should be no pinned portals anymore. Complain if someone + * leaked one. + */ + if (portal->portalPinned) + elog(ERROR, "cannot commit while a portal is pinned"); + /* * Do nothing to cursors held over from a previous transaction * (including holdable ones just frozen by CommitHoldablePortals). @@ -738,7 +774,15 @@ AtCleanup_Portals(void) continue; } - /* Else zap it. */ + /* + * If a portal is still pinned, forcibly unpin it. PortalDrop will + * not let us drop the portal otherwise. Whoever pinned the portal + * was interrupted by the abort too and won't try to use it anymore. + */ + if (portal->portalPinned) + portal->portalPinned = false; + + /* Zap it. */ PortalDrop(portal, false); } } diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h index 75101a782bf47412dd95c8db272dc543b5c85518..30214e68f3ae26a14980d1fb407d264426b8d1f0 100644 --- a/src/include/utils/portal.h +++ b/src/include/utils/portal.h @@ -39,7 +39,7 @@ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.82 2010/01/02 16:58:10 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/portal.h,v 1.83 2010/07/05 09:27:17 heikki Exp $ * *------------------------------------------------------------------------- */ @@ -133,6 +133,7 @@ typedef struct PortalData /* Status data */ PortalStatus status; /* see above */ + bool portalPinned; /* a pinned portal can't be dropped */ /* If not NULL, Executor is active; call ExecutorEnd eventually: */ QueryDesc *queryDesc; /* info needed for executor invocation */ @@ -199,6 +200,8 @@ extern void AtSubAbort_Portals(SubTransactionId mySubid, extern void AtSubCleanup_Portals(SubTransactionId mySubid); extern Portal CreatePortal(const char *name, bool allowDup, bool dupSilent); extern Portal CreateNewPortal(void); +extern void PinPortal(Portal portal); +extern void UnpinPortal(Portal portal); extern void PortalDrop(Portal portal, bool isTopCommit); extern Portal GetPortalByName(const char *name); extern void PortalDefineQuery(Portal portal, diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 3342e79150c38fe7ea038e0dd5beedcc15a49ac9..d1af4a6e5fbeece6e90d73c7d0833a4e0e2939f3 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.259 2010/06/21 09:47:29 heikki Exp $ + * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.260 2010/07/05 09:27:18 heikki Exp $ * *------------------------------------------------------------------------- */ @@ -2002,20 +2002,11 @@ exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt) rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false); /* ---------- - * Close portal. The statements executed in the loop might've closed the - * cursor already, rendering our portal pointer invalid, so we mustn't - * trust the pointer. + * Close portal, and restore cursor variable if it was initially NULL. * ---------- */ - portal = SPI_cursor_find(portalname); - if (portal == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_CURSOR), - errmsg("cursor \"%s\" closed unexpectedly", - portalname))); SPI_cursor_close(portal); - /* Restore cursor variable if it was initially NULL. */ if (curname == NULL) { free_var(curvar); @@ -4278,13 +4269,6 @@ exec_run_select(PLpgSQL_execstate *estate, * exec_for_query --- execute body of FOR loop for each row from a portal * * Used by exec_stmt_fors, exec_stmt_forc and exec_stmt_dynfors - * - * If the portal is for a cursor that's visible to user code, the statements - * we execute might move or close the cursor. You must pass prefetch_ok=false - * in that case to disable optimizations that rely on the portal staying - * unchanged over execution of the user statements. - * NB: With prefetch_ok=false, the portal pointer might point to garbage - * after the call. Caller beware! */ static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, @@ -4296,10 +4280,6 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, bool found = false; int rc = PLPGSQL_RC_OK; int n; - const char *portalname; - - /* Remember portal name so that we can re-find it */ - portalname = portal->name; /* * Determine if we assign to a record or a row @@ -4311,6 +4291,12 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, else elog(ERROR, "unsupported target"); + /* + * Make sure the portal doesn't get closed by the user statements + * we execute. + */ + PinPortal(portal); + /* * Fetch the initial tuple(s). If prefetching is allowed then we grab a * few more rows to avoid multiple trips through executor startup @@ -4408,22 +4394,8 @@ exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt, /* * Fetch more tuples. If prefetching is allowed, grab 50 at a time. - * Otherwise the statements executed in the loop might've moved or - * even closed the cursor, so check that the cursor is still open, - * and fetch only one row at a time. */ - if (prefetch_ok) - SPI_cursor_fetch(portal, true, 50); - else - { - portal = SPI_cursor_find(portalname); - if (portal == NULL) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_CURSOR), - errmsg("cursor \"%s\" closed unexpectedly", - portalname))); - SPI_cursor_fetch(portal, true, 1); - } + SPI_cursor_fetch(portal, true, prefetch_ok ? 50 : 1); tuptab = SPI_tuptable; n = SPI_processed; } @@ -4435,6 +4407,8 @@ loop_exit: */ SPI_freetuptable(tuptab); + UnpinPortal(portal); + /* * Set the FOUND variable to indicate the result of executing the loop * (namely, whether we looped one or more times). This must be set last so