]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/mmgr/portalmem.c
RESET SESSION, plus related new DDL commands. Patch from Marko Kreen,
[postgresql] / src / backend / utils / mmgr / portalmem.c
index e05eb5c7957e68c8d6f2d2bae57755dbcdd127af..eeba207dc94fdf5ea8b845672dcdd86d6d4c3351 100644 (file)
@@ -8,28 +8,33 @@
  * doesn't actually run the executor for them.
  *
  *
- * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.69 2004/08/25 18:43:43 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/utils/mmgr/portalmem.c,v 1.101 2007/04/12 06:53:48 neilc Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "access/heapam.h"
+#include "access/xact.h"
+#include "catalog/pg_type.h"
 #include "commands/portalcmds.h"
-#include "executor/executor.h"
-#include "utils/hsearch.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
 #include "utils/memutils.h"
-#include "utils/portal.h"
 
 /*
- * estimate of the maximum number of open portals a user would have,
- * used in initially sizing the PortalHashTable in EnablePortalManager()
+ * Estimate of the maximum number of open portals a user would have,
+ * used in initially sizing the PortalHashTable in EnablePortalManager().
+ * Since the hash table can expand, there's no need to make this overly
+ * generous, and keeping it small avoids unnecessary overhead in the
+ * hash_seq_search() calls executed during transaction end.
  */
-#define PORTALS_PER_USER          64
+#define PORTALS_PER_USER          16
 
 
 /* ----------------
@@ -49,12 +54,10 @@ static HTAB *PortalHashTable = NULL;
 
 #define PortalHashTableLookup(NAME, PORTAL) \
 do { \
-       PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
+       PortalHashEnt *hentry; \
        \
-       MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
-       hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
-                                                                                key, HASH_FIND, NULL); \
+       hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
+                                                                                  (NAME), HASH_FIND, NULL); \
        if (hentry) \
                PORTAL = hentry->portal; \
        else \
@@ -63,16 +66,10 @@ do { \
 
 #define PortalHashTableInsert(PORTAL, NAME) \
 do { \
-       PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
+       PortalHashEnt *hentry; bool found; \
        \
-       MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
-       hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
-                                                                                key, HASH_ENTER, &found); \
-       if (hentry == NULL) \
-               ereport(ERROR, \
-                               (errcode(ERRCODE_OUT_OF_MEMORY), \
-                                errmsg("out of memory"))); \
+       hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
+                                                                                  (NAME), HASH_ENTER, &found); \
        if (found) \
                elog(ERROR, "duplicate portal name"); \
        hentry->portal = PORTAL; \
@@ -82,12 +79,10 @@ do { \
 
 #define PortalHashTableDelete(PORTAL) \
 do { \
-       PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
+       PortalHashEnt *hentry; \
        \
-       MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
-       hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
-                                                                                key, HASH_REMOVE, NULL); \
+       hentry = (PortalHashEnt *) hash_search(PortalHashTable, \
+                                                                                  PORTAL->name, HASH_REMOVE, NULL); \
        if (hentry == NULL) \
                elog(WARNING, "trying to delete portal name that does not exist"); \
 } while(0)
@@ -145,6 +140,50 @@ GetPortalByName(const char *name)
        return portal;
 }
 
+/*
+ * PortalListGetPrimaryStmt
+ *             Get the "primary" stmt within a portal, ie, the one marked canSetTag.
+ *
+ * Returns NULL if no such stmt.  If multiple PlannedStmt structs within the
+ * portal are marked canSetTag, returns the first one. Neither of these
+ * cases should occur in present usages of this function.
+ *
+ * Copes if given a list of Querys --- can't happen in a portal, but this
+ * code also supports plancache.c, which needs both cases.
+ *
+ * Note: the reason this is just handed a List is so that plancache.c
+ * can share the code. For use with a portal, use PortalGetPrimaryStmt
+ * rather than calling this directly.
+ */
+Node *
+PortalListGetPrimaryStmt(List *stmts)
+{
+       ListCell   *lc;
+
+       foreach(lc, stmts)
+       {
+               Node   *stmt = (Node *) lfirst(lc);
+
+               if (IsA(stmt, PlannedStmt))
+               {
+                       if (((PlannedStmt *) stmt)->canSetTag)
+                               return stmt;
+               }
+               else if (IsA(stmt, Query))
+               {
+                       if (((Query *) stmt)->canSetTag)
+                               return stmt;
+               }
+               else
+               {
+                       /* Utility stmts are assumed canSetTag if they're the only stmt */
+                       if (list_length(stmts) == 1)
+                               return stmt;
+               }
+       }
+       return NULL;
+}
+
 /*
  * CreatePortal
  *             Returns a new portal given a name.
@@ -191,12 +230,15 @@ CreatePortal(const char *name, bool allowDup, bool dupSilent)
                                                                                   "Portal");
 
        /* initialize portal fields that don't start off zero */
+       portal->status = PORTAL_NEW;
        portal->cleanup = PortalCleanup;
-       portal->createXact = GetCurrentTransactionId();
+       portal->createSubid = GetCurrentSubTransactionId();
        portal->strategy = PORTAL_MULTI_QUERY;
        portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
        portal->atStart = true;
        portal->atEnd = true;           /* disallow fetches until query is set */
+       portal->visible = true;
+       portal->creation_time = GetCurrentStatementStartTimestamp();
 
        /* put portal in table (sets portal->name) */
        PortalHashTableInsert(portal, name);
@@ -233,31 +275,53 @@ CreateNewPortal(void)
  *
  * Notes: commandTag shall be NULL if and only if the original query string
  * (before rewriting) was an empty string.     Also, the passed commandTag must
- * be a pointer to a constant string, since it is not copied.  The caller is
- * responsible for ensuring that the passed sourceText (if any), parse and
- * plan trees have adequate lifetime.  Also, queryContext must accurately
- * describe the location of the parse and plan trees.
+ * be a pointer to a constant string, since it is not copied.  However,
+ * prepStmtName and sourceText, if provided, are copied into the portal's
+ * heap context for safekeeping.
+ *
+ * If cplan is provided, then it is a cached plan containing the stmts,
+ * and the caller must have done RevalidateCachedPlan(), causing a refcount
+ * increment.  The refcount will be released when the portal is destroyed.
+ *
+ * If cplan is NULL, then it is the caller's responsibility to ensure that
+ * the passed plan trees have adequate lifetime.  Typically this is done by
+ * copying them into the portal's heap context.
  */
 void
 PortalDefineQuery(Portal portal,
+                                 const char *prepStmtName,
                                  const char *sourceText,
                                  const char *commandTag,
-                                 List *parseTrees,
-                                 List *planTrees,
-                                 MemoryContext queryContext)
+                                 List *stmts,
+                                 CachedPlan *cplan)
 {
        AssertArg(PortalIsValid(portal));
-       AssertState(portal->queryContext == NULL);      /* else defined already */
+       AssertState(portal->status == PORTAL_NEW);
 
-       Assert(list_length(parseTrees) == list_length(planTrees));
+       Assert(commandTag != NULL || stmts == NIL);
 
-       Assert(commandTag != NULL || parseTrees == NIL);
-
-       portal->sourceText = sourceText;
+       portal->prepStmtName = prepStmtName ? 
+               MemoryContextStrdup(PortalGetHeapMemory(portal), prepStmtName) : NULL;
+       portal->sourceText = sourceText ? 
+               MemoryContextStrdup(PortalGetHeapMemory(portal), sourceText) : NULL;
        portal->commandTag = commandTag;
-       portal->parseTrees = parseTrees;
-       portal->planTrees = planTrees;
-       portal->queryContext = queryContext;
+       portal->stmts = stmts;
+       portal->cplan = cplan;
+       portal->status = PORTAL_DEFINED;
+}
+
+/*
+ * PortalReleaseCachedPlan
+ *             Release a portal's reference to its cached plan, if any.
+ */
+static void
+PortalReleaseCachedPlan(Portal portal)
+{
+       if (portal->cplan)
+       {
+               ReleaseCachedPlan(portal->cplan, false);
+               portal->cplan = NULL;
+       }
 }
 
 /*
@@ -273,12 +337,12 @@ PortalCreateHoldStore(Portal portal)
        Assert(portal->holdStore == NULL);
 
        /*
-        * Create the memory context that is used for storage of the tuple
-        * set. Note this is NOT a child of the portal's heap memory.
+        * Create the memory context that is used for storage of the tuple set.
+        * Note this is NOT a child of the portal's heap memory.
         */
        portal->holdContext =
                AllocSetContextCreate(PortalMemory,
-                                                         "PortalHeapMemory",
+                                                         "PortalHoldContext",
                                                          ALLOCSET_DEFAULT_MINSIZE,
                                                          ALLOCSET_DEFAULT_INITSIZE,
                                                          ALLOCSET_DEFAULT_MAXSIZE);
@@ -306,10 +370,10 @@ PortalDrop(Portal portal, bool isTopCommit)
                elog(ERROR, "cannot drop active portal");
 
        /*
-        * Remove portal from hash table.  Because we do this first, we will
-        * not come back to try to remove the portal again if there's any
-        * error in the subsequent steps.  Better to leak a little memory than
-        * to get into an infinite error-recovery loop.
+        * Remove portal from hash table.  Because we do this first, we will not
+        * come back to try to remove the portal again if there's any error in the
+        * subsequent steps.  Better to leak a little memory than to get into an
+        * infinite error-recovery loop.
         */
        PortalHashTableDelete(portal);
 
@@ -317,33 +381,37 @@ PortalDrop(Portal portal, bool isTopCommit)
        if (PointerIsValid(portal->cleanup))
                (*portal->cleanup) (portal);
 
+       /* drop cached plan reference, if any */
+       if (portal->cplan)
+               PortalReleaseCachedPlan(portal);
+
        /*
-        * Release any resources still attached to the portal.  There are
-        * several cases being covered here:
+        * Release any resources still attached to the portal.  There are several
+        * cases being covered here:
         *
         * Top transaction commit (indicated by isTopCommit): normally we should
         * do nothing here and let the regular end-of-transaction resource
-        * releasing mechanism handle these resources too.  However, if we have
-        * a FAILED portal (eg, a cursor that got an error), we'd better clean
-        * up its resources to avoid resource-leakage warning messages.
+        * releasing mechanism handle these resources too.      However, if we have a
+        * FAILED portal (eg, a cursor that got an error), we'd better clean up
+        * its resources to avoid resource-leakage warning messages.
         *
-        * Sub transaction commit: never comes here at all, since we don't
-        * kill any portals in AtSubCommit_Portals().
+        * Sub transaction commit: never comes here at all, since we don't kill
+        * any portals in AtSubCommit_Portals().
         *
         * Main or sub transaction abort: we will do nothing here because
         * portal->resowner was already set NULL; the resources were already
         * cleaned up in transaction abort.
         *
         * Ordinary portal drop: must release resources.  However, if the portal
-        * is not FAILED then we do not release its locks.  The locks become
-        * the responsibility of the transaction's ResourceOwner (since it is
-        * the parent of the portal's owner) and will be released when the
-        * transaction eventually ends.
+        * is not FAILED then we do not release its locks.      The locks become the
+        * responsibility of the transaction's ResourceOwner (since it is the
+        * parent of the portal's owner) and will be released when the transaction
+        * eventually ends.
         */
        if (portal->resowner &&
                (!isTopCommit || portal->status == PORTAL_FAILED))
        {
-               bool    isCommit = (portal->status != PORTAL_FAILED);
+               bool            isCommit = (portal->status != PORTAL_FAILED);
 
                ResourceOwnerRelease(portal->resowner,
                                                         RESOURCE_RELEASE_BEFORE_LOCKS,
@@ -385,24 +453,24 @@ PortalDrop(Portal portal, bool isTopCommit)
 }
 
 /*
- * DropDependentPortals
- *             Drop any portals using the specified context as queryContext.
+ * Delete all declared cursors.
  *
- * This is normally used to make sure we can safely drop a prepared statement.
+ * Used by commands: CLOSE ALL, RESET SESSION
  */
 void
-DropDependentPortals(MemoryContext queryContext)
+PortalHashTableDeleteAll(void)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
 
-       hash_seq_init(&status, PortalHashTable);
+       if (PortalHashTable == NULL)
+               return;
 
-       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       hash_seq_init(&status, PortalHashTable);
+       while ((hentry = hash_seq_search(&status)) != NULL)
        {
-               Portal          portal = hentry->portal;
-
-               if (portal->queryContext == queryContext)
+               Portal portal = hentry->portal;
+               if (portal->status != PORTAL_ACTIVE)
                        PortalDrop(portal, false);
        }
 }
@@ -413,21 +481,16 @@ DropDependentPortals(MemoryContext queryContext)
  *
  * Any holdable cursors created in this transaction need to be converted to
  * materialized form, since we are going to close down the executor and
- * release locks.  Remove all other portals created in this transaction.
- * Portals remaining from prior transactions should be left untouched.
+ * release locks.  Other portals are not touched yet.
  *
- * XXX This assumes that portals can be deleted in a random order, ie,
- * no portal has a reference to any other (at least not one that will be
- * exercised during deletion). I think this is okay at the moment, but
- * we've had bugs of that ilk in the past.  Keep a close eye on cursor
- * references...
+ * Returns TRUE if any holdable cursors were processed, FALSE if not.
  */
-void
-AtCommit_Portals(void)
+bool
+CommitHoldablePortals(void)
 {
+       bool            result = false;
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId xact = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
@@ -435,56 +498,131 @@ AtCommit_Portals(void)
        {
                Portal          portal = hentry->portal;
 
-               /*
-                * Do not touch active portals --- this can only happen in the
-                * case of a multi-transaction utility command, such as VACUUM.
-                *
-                * Note however that any resource owner attached to such a portal
-                * is still going to go away, so don't leave a dangling pointer.
-                */
-               if (portal->status == PORTAL_ACTIVE)
-               {
-                       portal->resowner = NULL;
-                       continue;
-               }
-
-               /*
-                * Do nothing else to cursors held over from a previous
-                * transaction. (This test must include checking CURSOR_OPT_HOLD,
-                * else we will fail to clean up a VACUUM portal if it fails after
-                * its first sub-transaction.)
-                */
-               if (portal->createXact != xact &&
-                       (portal->cursorOptions & CURSOR_OPT_HOLD))
-                       continue;
-
+               /* Is it a holdable portal created in the current xact? */
                if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
+                       portal->createSubid != InvalidSubTransactionId &&
                        portal->status == PORTAL_READY)
                {
                        /*
-                        * We are exiting the transaction that created a holdable
-                        * cursor.      Instead of dropping the portal, prepare it for
-                        * access by later transactions.
+                        * We are exiting the transaction that created a holdable cursor.
+                        * Instead of dropping the portal, prepare it for access by later
+                        * transactions.
                         *
                         * Note that PersistHoldablePortal() must release all resources
-                        * used by the portal that are local to the creating
-                        * transaction.
+                        * used by the portal that are local to the creating transaction.
                         */
                        PortalCreateHoldStore(portal);
                        PersistHoldablePortal(portal);
 
+                       /* drop cached plan reference, if any */
+                       if (portal->cplan)
+                               PortalReleaseCachedPlan(portal);
+
                        /*
                         * Any resources belonging to the portal will be released in the
-                        * upcoming transaction-wide cleanup; the portal will no
-                        * longer have its own resources.
+                        * upcoming transaction-wide cleanup; the portal will no longer
+                        * have its own resources.
                         */
                        portal->resowner = NULL;
+
+                       /*
+                        * Having successfully exported the holdable cursor, mark it as
+                        * not belonging to this transaction.
+                        */
+                       portal->createSubid = InvalidSubTransactionId;
+
+                       result = true;
                }
-               else
+       }
+
+       return result;
+}
+
+/*
+ * Pre-prepare processing for portals.
+ *
+ * Currently we refuse PREPARE if the transaction created any holdable
+ * cursors, since it's quite unclear what to do with one.  However, this
+ * has the same API as CommitHoldablePortals and is invoked in the same
+ * way by xact.c, so that we can easily do something reasonable if anyone
+ * comes up with something reasonable to do.
+ *
+ * Returns TRUE if any holdable cursors were processed, FALSE if not.
+ */
+bool
+PrepareHoldablePortals(void)
+{
+       bool            result = false;
+       HASH_SEQ_STATUS status;
+       PortalHashEnt *hentry;
+
+       hash_seq_init(&status, PortalHashTable);
+
+       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       {
+               Portal          portal = hentry->portal;
+
+               /* Is it a holdable portal created in the current xact? */
+               if ((portal->cursorOptions & CURSOR_OPT_HOLD) &&
+                       portal->createSubid != InvalidSubTransactionId &&
+                       portal->status == PORTAL_READY)
+               {
+                       /*
+                        * We are exiting the transaction that created a holdable cursor.
+                        * Can't do PREPARE.
+                        */
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                        errmsg("cannot PREPARE a transaction that has created a cursor WITH HOLD")));
+               }
+       }
+
+       return result;
+}
+
+/*
+ * Pre-commit processing for portals.
+ *
+ * Remove all non-holdable portals created in this transaction.
+ * Portals remaining from prior transactions should be left untouched.
+ */
+void
+AtCommit_Portals(void)
+{
+       HASH_SEQ_STATUS status;
+       PortalHashEnt *hentry;
+
+       hash_seq_init(&status, PortalHashTable);
+
+       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       {
+               Portal          portal = hentry->portal;
+
+               /*
+                * Do not touch active portals --- this can only happen in the case of
+                * a multi-transaction utility command, such as VACUUM.
+                *
+                * Note however that any resource owner attached to such a portal is
+                * still going to go away, so don't leave a dangling pointer.
+                */
+               if (portal->status == PORTAL_ACTIVE)
                {
-                       /* Zap all non-holdable portals */
-                       PortalDrop(portal, true);
+                       portal->resowner = NULL;
+                       continue;
                }
+
+               /*
+                * Do nothing to cursors held over from a previous transaction
+                * (including holdable ones just frozen by CommitHoldablePortals).
+                */
+               if (portal->createSubid == InvalidSubTransactionId)
+                       continue;
+
+               /* Zap all non-holdable portals */
+               PortalDrop(portal, true);
+
+               /* Restart the iteration */
+               hash_seq_init(&status, PortalHashTable);
        }
 }
 
@@ -492,7 +630,7 @@ AtCommit_Portals(void)
  * Abort processing for portals.
  *
  * At this point we reset "active" status and run the cleanup hook if
- * present, but we can't release memory until the cleanup call.
+ * present, but we can't release the portal's memory until the cleanup call.
  *
  * The reason we need to reset active is so that we can replace the unnamed
  * portal, else we'll fail to execute ROLLBACK when it arrives.
@@ -502,7 +640,6 @@ AtAbort_Portals(void)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId xact = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
@@ -514,13 +651,9 @@ AtAbort_Portals(void)
                        portal->status = PORTAL_FAILED;
 
                /*
-                * Do nothing else to cursors held over from a previous
-                * transaction. (This test must include checking CURSOR_OPT_HOLD,
-                * else we will fail to clean up a VACUUM portal if it fails after
-                * its first sub-transaction.)
+                * Do nothing else to cursors held over from a previous transaction.
                 */
-               if (portal->createXact != xact &&
-                       (portal->cursorOptions & CURSOR_OPT_HOLD))
+               if (portal->createSubid == InvalidSubTransactionId)
                        continue;
 
                /* let portalcmds.c clean up the state it knows about */
@@ -529,12 +662,25 @@ AtAbort_Portals(void)
                        (*portal->cleanup) (portal);
                        portal->cleanup = NULL;
                }
+
+               /* drop cached plan reference, if any */
+               if (portal->cplan)
+                       PortalReleaseCachedPlan(portal);
+
                /*
                 * Any resources belonging to the portal will be released in the
-                * upcoming transaction-wide cleanup; they will be gone before
-                * we run PortalDrop.
+                * upcoming transaction-wide cleanup; they will be gone before we run
+                * PortalDrop.
                 */
                portal->resowner = NULL;
+
+               /*
+                * Although we can't delete the portal data structure proper, we can
+                * release any memory in subsidiary contexts, such as executor state.
+                * The cleanup hook was the last thing that might have needed data
+                * there.
+                */
+               MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
        }
 }
 
@@ -547,7 +693,6 @@ AtCleanup_Portals(void)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId xact = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
@@ -555,14 +700,8 @@ AtCleanup_Portals(void)
        {
                Portal          portal = hentry->portal;
 
-               /*
-                * Do nothing else to cursors held over from a previous
-                * transaction. (This test must include checking CURSOR_OPT_HOLD,
-                * else we will fail to clean up a VACUUM portal if it fails after
-                * its first sub-transaction.)
-                */
-               if (portal->createXact != xact &&
-                       (portal->cursorOptions & CURSOR_OPT_HOLD))
+               /* Do nothing to cursors held over from a previous transaction */
+               if (portal->createSubid == InvalidSubTransactionId)
                {
                        Assert(portal->status != PORTAL_ACTIVE);
                        Assert(portal->resowner == NULL);
@@ -578,25 +717,25 @@ AtCleanup_Portals(void)
  * Pre-subcommit processing for portals.
  *
  * Reassign the portals created in the current subtransaction to the parent
- * transaction.
+ * subtransaction.
  */
 void
-AtSubCommit_Portals(TransactionId parentXid,
+AtSubCommit_Portals(SubTransactionId mySubid,
+                                       SubTransactionId parentSubid,
                                        ResourceOwner parentXactOwner)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId curXid = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
        while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
        {
-               Portal  portal = hentry->portal;
+               Portal          portal = hentry->portal;
 
-               if (portal->createXact == curXid)
+               if (portal->createSubid == mySubid)
                {
-                       portal->createXact = parentXid;
+                       portal->createSubid = parentSubid;
                        if (portal->resowner)
                                ResourceOwnerNewParent(portal->resowner, parentXactOwner);
                }
@@ -606,47 +745,59 @@ AtSubCommit_Portals(TransactionId parentXid,
 /*
  * Subtransaction abort handling for portals.
  *
- * Deactivate failed portals created during the failed subtransaction.
+ * Deactivate portals created during the failed subtransaction.
  * Note that per AtSubCommit_Portals, this will catch portals created
  * in descendants of the subtransaction too.
+ *
+ * We don't destroy any portals here; that's done in AtSubCleanup_Portals.
  */
 void
-AtSubAbort_Portals(TransactionId parentXid,
+AtSubAbort_Portals(SubTransactionId mySubid,
+                                  SubTransactionId parentSubid,
                                   ResourceOwner parentXactOwner)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId curXid = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
        while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
        {
-               Portal  portal = hentry->portal;
+               Portal          portal = hentry->portal;
 
-               if (portal->createXact != curXid)
+               if (portal->createSubid != mySubid)
                        continue;
 
                /*
                 * Force any active portals of my own transaction into FAILED state.
                 * This is mostly to ensure that a portal running a FETCH will go
-                * FAILED if the underlying cursor fails.  (Note we do NOT want to
-                * do this to upper-level portals, since they may be able to continue.)
+                * FAILED if the underlying cursor fails.  (Note we do NOT want to do
+                * this to upper-level portals, since they may be able to continue.)
+                *
+                * This is only needed to dodge the sanity check in PortalDrop.
                 */
-               if (portal->status == PORTAL_ACTIVE)
+               if (portal->status == PORTAL_ACTIVE)
                        portal->status = PORTAL_FAILED;
 
                /*
-                * If the portal is READY then allow it to survive into the
-                * parent transaction; otherwise shut it down.
+                * If the portal is READY then allow it to survive into the parent
+                * transaction; otherwise shut it down.
+                *
+                * Currently, we can't actually support that because the portal's
+                * query might refer to objects created or changed in the failed
+                * subtransaction, leading to crashes if execution is resumed. So,
+                * even READY portals are deleted.      It would be nice to detect whether
+                * the query actually depends on any such object, instead.
                 */
+#ifdef NOT_USED
                if (portal->status == PORTAL_READY)
                {
-                       portal->createXact = parentXid;
+                       portal->createSubid = parentSubid;
                        if (portal->resowner)
                                ResourceOwnerNewParent(portal->resowner, parentXactOwner);
                }
                else
+#endif
                {
                        /* let portalcmds.c clean up the state it knows about */
                        if (PointerIsValid(portal->cleanup))
@@ -654,12 +805,25 @@ AtSubAbort_Portals(TransactionId parentXid,
                                (*portal->cleanup) (portal);
                                portal->cleanup = NULL;
                        }
+
+                       /* drop cached plan reference, if any */
+                       if (portal->cplan)
+                               PortalReleaseCachedPlan(portal);
+
                        /*
                         * Any resources belonging to the portal will be released in the
-                        * upcoming transaction-wide cleanup; they will be gone before
-                        * we run PortalDrop.
+                        * upcoming transaction-wide cleanup; they will be gone before we
+                        * run PortalDrop.
                         */
                        portal->resowner = NULL;
+
+                       /*
+                        * Although we can't delete the portal data structure proper, we
+                        * can release any memory in subsidiary contexts, such as executor
+                        * state.  The cleanup hook was the last thing that might have
+                        * needed data there.
+                        */
+                       MemoryContextDeleteChildren(PortalGetHeapMemory(portal));
                }
        }
 }
@@ -671,11 +835,10 @@ AtSubAbort_Portals(TransactionId parentXid,
  * we will not drop any that were reassigned to the parent above).
  */
 void
-AtSubCleanup_Portals(void)
+AtSubCleanup_Portals(SubTransactionId mySubid)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
-       TransactionId curXid = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
@@ -683,14 +846,108 @@ AtSubCleanup_Portals(void)
        {
                Portal          portal = hentry->portal;
 
-               if (portal->createXact != curXid)
+               if (portal->createSubid != mySubid)
                        continue;
 
-               /* AtSubAbort_Portals should have fixed these: */
-               Assert(portal->status != PORTAL_ACTIVE);
-               Assert(portal->resowner == NULL);
-
                /* Zap it. */
                PortalDrop(portal, false);
        }
 }
+
+/* Find all available cursors */
+Datum
+pg_cursor(PG_FUNCTION_ARGS)
+{
+       FuncCallContext *funcctx;
+       HASH_SEQ_STATUS *hash_seq;
+       PortalHashEnt *hentry;
+
+       /* stuff done only on the first call of the function */
+       if (SRF_IS_FIRSTCALL())
+       {
+               MemoryContext oldcontext;
+               TupleDesc       tupdesc;
+
+               /* create a function context for cross-call persistence */
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               /*
+                * switch to memory context appropriate for multiple function calls
+                */
+               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               if (PortalHashTable)
+               {
+                       hash_seq = (HASH_SEQ_STATUS *) palloc(sizeof(HASH_SEQ_STATUS));
+                       hash_seq_init(hash_seq, PortalHashTable);
+                       funcctx->user_fctx = (void *) hash_seq;
+               }
+               else
+                       funcctx->user_fctx = NULL;
+
+               /*
+                * build tupdesc for result tuples. This must match the definition of
+                * the pg_cursors view in system_views.sql
+                */
+               tupdesc = CreateTemplateTupleDesc(6, false);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
+                                                  TEXTOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 2, "statement",
+                                                  TEXTOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 3, "is_holdable",
+                                                  BOOLOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 4, "is_binary",
+                                                  BOOLOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 5, "is_scrollable",
+                                                  BOOLOID, -1, 0);
+               TupleDescInitEntry(tupdesc, (AttrNumber) 6, "creation_time",
+                                                  TIMESTAMPTZOID, -1, 0);
+
+               funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+               MemoryContextSwitchTo(oldcontext);
+       }
+
+       /* stuff done on every call of the function */
+       funcctx = SRF_PERCALL_SETUP();
+       hash_seq = (HASH_SEQ_STATUS *) funcctx->user_fctx;
+
+       /* if the hash table is uninitialized, we're done */
+       if (hash_seq == NULL)
+               SRF_RETURN_DONE(funcctx);
+
+       /* loop until we find a visible portal or hit the end of the list */
+       while ((hentry = hash_seq_search(hash_seq)) != NULL)
+       {
+               if (hentry->portal->visible)
+                       break;
+       }
+
+       if (hentry)
+       {
+               Portal          portal;
+               Datum           result;
+               HeapTuple       tuple;
+               Datum           values[6];
+               bool            nulls[6];
+
+               portal = hentry->portal;
+               MemSet(nulls, 0, sizeof(nulls));
+
+               values[0] = DirectFunctionCall1(textin, CStringGetDatum(portal->name));
+               if (!portal->sourceText)
+                       nulls[1] = true;
+               else
+                       values[1] = DirectFunctionCall1(textin,
+                                                                               CStringGetDatum(portal->sourceText));
+               values[2] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_HOLD);
+               values[3] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_BINARY);
+               values[4] = BoolGetDatum(portal->cursorOptions & CURSOR_OPT_SCROLL);
+               values[5] = TimestampTzGetDatum(portal->creation_time);
+
+               tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+               result = HeapTupleGetDatum(tuple);
+               SRF_RETURN_NEXT(funcctx, result);
+       }
+
+       SRF_RETURN_DONE(funcctx);
+}