]> granicus.if.org Git - postgresql/blobdiff - src/backend/utils/mmgr/portalmem.c
Update copyrights to 2003.
[postgresql] / src / backend / utils / mmgr / portalmem.c
index 25171ea7bee94a03ba0c5b3bc1330fee120abe80..3e51ad2b36c96e493f0fff4d823e93cf42904f54 100644 (file)
@@ -1,48 +1,43 @@
 /*-------------------------------------------------------------------------
  *
  * portalmem.c
- *       backend portal memory context management stuff
+ *       backend portal memory management
+ *
+ * Portals are objects representing the execution state of a query.
+ * This module provides memory management services for portals, but it
+ * doesn't actually run the executor for them.
  *
- * Portions Copyright (c) 1996-2001, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
  *
+ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.44 2001/10/25 05:49:51 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/utils/mmgr/portalmem.c,v 1.61 2003/08/04 02:40:08 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
-/*
- * NOTES
- *             A "Portal" is a structure used to keep track of cursor queries.
- *
- *             When the backend sees a "declare cursor" query, it allocates a
- *             "PortalData" structure, plans the query and then stores the query
- *             in the portal without executing it.  Later, when the backend
- *             sees a
- *                             fetch 1 from FOO
- *             the system looks up the portal named "FOO" in the portal table,
- *             gets the planned query and then calls the executor with a feature of
- *             '(EXEC_FOR 1).  The executor then runs the query and returns a single
- *             tuple.  The problem is that we have to hold onto the state of the
- *             portal query until we see a "close".  This means we have to be
- *             careful about memory management.
- *
- *             I hope this makes things clearer to whoever reads this -cim 2/22/91
- */
-
 #include "postgres.h"
 
+#include "miscadmin.h"
+#include "commands/portalcmds.h"
+#include "executor/executor.h"
 #include "utils/hsearch.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()
+ */
+#define PORTALS_PER_USER          64
+
+
 /* ----------------
  *             Global state
  * ----------------
  */
 
-#define MAX_PORTALNAME_LEN             64
+#define MAX_PORTALNAME_LEN             NAMEDATALEN
 
 typedef struct portalhashent
 {
@@ -57,7 +52,7 @@ do { \
        PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
        \
        MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", NAME); \
+       StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
        hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
                                                                                 key, HASH_FIND, NULL); \
        if (hentry) \
@@ -66,19 +61,23 @@ do { \
                PORTAL = NULL; \
 } while(0)
 
-#define PortalHashTableInsert(PORTAL) \
+#define PortalHashTableInsert(PORTAL, NAME) \
 do { \
        PortalHashEnt *hentry; bool found; char key[MAX_PORTALNAME_LEN]; \
        \
        MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+       StrNCpy(key, NAME, MAX_PORTALNAME_LEN); \
        hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
                                                                                 key, HASH_ENTER, &found); \
        if (hentry == NULL) \
-               elog(ERROR, "out of memory in PortalHashTable"); \
+               ereport(ERROR, \
+                               (errcode(ERRCODE_OUT_OF_MEMORY), \
+                                errmsg("out of memory"))); \
        if (found) \
-               elog(NOTICE, "trying to insert a portal name that exists."); \
+               elog(ERROR, "duplicate portal name"); \
        hentry->portal = PORTAL; \
+       /* To avoid duplicate storage, make PORTAL->name point to htab entry */ \
+       PORTAL->name = hentry->portalname; \
 } while(0)
 
 #define PortalHashTableDelete(PORTAL) \
@@ -86,11 +85,11 @@ do { \
        PortalHashEnt *hentry; char key[MAX_PORTALNAME_LEN]; \
        \
        MemSet(key, 0, MAX_PORTALNAME_LEN); \
-       snprintf(key, MAX_PORTALNAME_LEN - 1, "%s", PORTAL->name); \
+       StrNCpy(key, PORTAL->name, MAX_PORTALNAME_LEN); \
        hentry = (PortalHashEnt*)hash_search(PortalHashTable, \
                                                                                 key, HASH_REMOVE, NULL); \
        if (hentry == NULL) \
-               elog(NOTICE, "trying to delete portal name that does not exist."); \
+               elog(WARNING, "trying to delete portal name that does not exist"); \
 } while(0)
 
 static MemoryContext PortalMemory = NULL;
@@ -122,8 +121,8 @@ EnablePortalManager(void)
        ctl.entrysize = sizeof(PortalHashEnt);
 
        /*
-        * use PORTALS_PER_USER, defined in utils/portal.h as a guess of how
-        * many hash table entries to create, initially
+        * use PORTALS_PER_USER as a guess of how many hash table entries to
+        * create, initially
         */
        PortalHashTable = hash_create("Portal hash", PORTALS_PER_USER,
                                                                  &ctl, HASH_ELEM);
@@ -134,7 +133,7 @@ EnablePortalManager(void)
  *             Returns a portal given a portal name, or NULL if name not found.
  */
 Portal
-GetPortalByName(char *name)
+GetPortalByName(const char *name)
 {
        Portal          portal;
 
@@ -146,45 +145,17 @@ GetPortalByName(char *name)
        return portal;
 }
 
-/*
- * PortalSetQuery
- *             Attaches a "query" to portal.
- *
- * Exceptions:
- *             BadState if called when disabled.
- *             BadArg if portal is invalid.
- *             BadArg if queryDesc is "invalid."
- *             BadArg if state is "invalid."
- */
-void
-PortalSetQuery(Portal portal,
-                          QueryDesc *queryDesc,
-                          TupleDesc attinfo,
-                          EState *state,
-                          void (*cleanup) (Portal portal))
-{
-       AssertArg(PortalIsValid(portal));
-       AssertArg(IsA((Node *) state, EState));
-
-       portal->queryDesc = queryDesc;
-       portal->attinfo = attinfo;
-       portal->state = state;
-       portal->atStart = true;         /* Allow fetch forward only */
-       portal->atEnd = false;
-       portal->cleanup = cleanup;
-}
-
 /*
  * CreatePortal
  *             Returns a new portal given a name.
  *
- * Exceptions:
- *             BadState if called when disabled.
- *             BadArg if portal name is invalid.
- *             "NOTICE" if portal name is in use (existing portal is returned!)
+ * allowDup: if true, automatically drop any pre-existing portal of the
+ * same name (if false, an error is raised).
+ *
+ * dupSilent: if true, don't even emit a WARNING.
  */
 Portal
-CreatePortal(char *name)
+CreatePortal(const char *name, bool allowDup, bool dupSilent)
 {
        Portal          portal;
 
@@ -193,67 +164,215 @@ CreatePortal(char *name)
        portal = GetPortalByName(name);
        if (PortalIsValid(portal))
        {
-               elog(NOTICE, "CreatePortal: portal \"%s\" already exists", name);
-               return portal;
+               if (!allowDup)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_DUPLICATE_CURSOR),
+                                        errmsg("portal \"%s\" already exists", name)));
+               if (!dupSilent)
+                       ereport(WARNING,
+                                       (errcode(ERRCODE_DUPLICATE_CURSOR),
+                                        errmsg("closing pre-existing portal \"%s\"",
+                                                       name)));
+               PortalDrop(portal, false);
        }
 
        /* make new portal structure */
-       portal = (Portal) MemoryContextAlloc(PortalMemory, sizeof *portal);
-
-       /* initialize portal name */
-       portal->name = MemoryContextStrdup(PortalMemory, name);
+       portal = (Portal) MemoryContextAllocZero(PortalMemory, sizeof *portal);
 
-       /* initialize portal heap context */
+       /* initialize portal heap context; typically it won't store much */
        portal->heap = AllocSetContextCreate(PortalMemory,
                                                                                 "PortalHeapMemory",
-                                                                                ALLOCSET_DEFAULT_MINSIZE,
-                                                                                ALLOCSET_DEFAULT_INITSIZE,
-                                                                                ALLOCSET_DEFAULT_MAXSIZE);
+                                                                                ALLOCSET_SMALL_MINSIZE,
+                                                                                ALLOCSET_SMALL_INITSIZE,
+                                                                                ALLOCSET_SMALL_MAXSIZE);
 
-       /* initialize portal query */
-       portal->queryDesc = NULL;
-       portal->attinfo = NULL;
-       portal->state = NULL;
-       portal->atStart = true;         /* disallow fetches until query is set */
-       portal->atEnd = true;
-       portal->cleanup = NULL;
+       /* initialize portal fields that don't start off zero */
+       portal->cleanup = PortalCleanup;
+       portal->createXact = GetCurrentTransactionId();
+       portal->strategy = PORTAL_MULTI_QUERY;
+       portal->cursorOptions = CURSOR_OPT_NO_SCROLL;
+       portal->atStart = true;
+       portal->atEnd = true;           /* disallow fetches until query is set */
 
-       /* put portal in table */
-       PortalHashTableInsert(portal);
+       /* put portal in table (sets portal->name) */
+       PortalHashTableInsert(portal, name);
 
        return portal;
 }
 
+/*
+ * CreateNewPortal
+ *             Create a new portal, assigning it a random nonconflicting name.
+ */
+Portal
+CreateNewPortal(void)
+{
+       static unsigned int unnamed_portal_count = 0;
+
+       char            portalname[MAX_PORTALNAME_LEN];
+
+       /* Select a nonconflicting name */
+       for (;;)
+       {
+               unnamed_portal_count++;
+               sprintf(portalname, "<unnamed portal %u>", unnamed_portal_count);
+               if (GetPortalByName(portalname) == NULL)
+                       break;
+       }
+
+       return CreatePortal(portalname, false, false);
+}
+
+/*
+ * PortalDefineQuery
+ *             A simple subroutine to establish a portal's query.
+ *
+ * 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.
+ */
+void
+PortalDefineQuery(Portal portal,
+                                 const char *sourceText,
+                                 const char *commandTag,
+                                 List *parseTrees,
+                                 List *planTrees,
+                                 MemoryContext queryContext)
+{
+       AssertArg(PortalIsValid(portal));
+       AssertState(portal->queryContext == NULL);      /* else defined already */
+
+       Assert(length(parseTrees) == length(planTrees));
+
+       Assert(commandTag != NULL || parseTrees == NIL);
+
+       portal->sourceText = sourceText;
+       portal->commandTag = commandTag;
+       portal->parseTrees = parseTrees;
+       portal->planTrees = planTrees;
+       portal->queryContext = queryContext;
+}
+
+/*
+ * PortalCreateHoldStore
+ *             Create the tuplestore for a portal.
+ */
+void
+PortalCreateHoldStore(Portal portal)
+{
+       MemoryContext oldcxt;
+
+       Assert(portal->holdContext == NULL);
+       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.
+        */
+       portal->holdContext =
+               AllocSetContextCreate(PortalMemory,
+                                                         "PortalHeapMemory",
+                                                         ALLOCSET_DEFAULT_MINSIZE,
+                                                         ALLOCSET_DEFAULT_INITSIZE,
+                                                         ALLOCSET_DEFAULT_MAXSIZE);
+
+       /* Create the tuple store, selecting cross-transaction temp files. */
+       oldcxt = MemoryContextSwitchTo(portal->holdContext);
+
+       /* XXX: Should SortMem be used for this? */
+       portal->holdStore = tuplestore_begin_heap(true, true, SortMem);
+
+       MemoryContextSwitchTo(oldcxt);
+}
+
 /*
  * PortalDrop
- *             Destroys portal.
+ *             Destroy the portal.
  *
- * Exceptions:
- *             BadState if called when disabled.
- *             BadArg if portal is invalid.
+ *             isError: if true, we are destroying portals at the end of a failed
+ *             transaction.  (This causes PortalCleanup to skip unneeded steps.)
  */
 void
-PortalDrop(Portal portal)
+PortalDrop(Portal portal, bool isError)
 {
        AssertArg(PortalIsValid(portal));
 
-       /* remove portal from hash table */
+       /* Not sure if this case can validly happen or not... */
+       if (portal->portalActive)
+               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.
+        */
        PortalHashTableDelete(portal);
 
-       /* reset portal */
+       /* let portalcmds.c clean up the state it knows about */
        if (PointerIsValid(portal->cleanup))
-               (*portal->cleanup) (portal);
+               (*portal->cleanup) (portal, isError);
+
+       /*
+        * Delete tuplestore if present.  We should do this even under error
+        * conditions; since the tuplestore would have been using cross-
+        * transaction storage, its temp files need to be explicitly deleted.
+        */
+       if (portal->holdStore)
+       {
+               MemoryContext oldcontext;
+
+               oldcontext = MemoryContextSwitchTo(portal->holdContext);
+               tuplestore_end(portal->holdStore);
+               MemoryContextSwitchTo(oldcontext);
+               portal->holdStore = NULL;
+       }
+
+       /* delete tuplestore storage, if any */
+       if (portal->holdContext)
+               MemoryContextDelete(portal->holdContext);
 
        /* release subsidiary storage */
        MemoryContextDelete(PortalGetHeapMemory(portal));
 
-       /* release name and portal data (both are in PortalMemory) */
-       pfree(portal->name);
+       /* release portal struct (it's in PortalMemory) */
        pfree(portal);
 }
 
 /*
- * Destroy all portals created in the current transaction (ie, all of them).
+ * DropDependentPortals
+ *             Drop any portals using the specified context as queryContext.
+ *
+ * This is normally used to make sure we can safely drop a prepared statement.
+ */
+void
+DropDependentPortals(MemoryContext queryContext)
+{
+       HASH_SEQ_STATUS status;
+       PortalHashEnt *hentry;
+
+       hash_seq_init(&status, PortalHashTable);
+
+       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       {
+               Portal          portal = hentry->portal;
+
+               if (portal->queryContext == queryContext)
+                       PortalDrop(portal, false);
+       }
+}
+
+
+/*
+ * Pre-commit processing for portals.
+ *
+ * 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.
  *
  * 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
@@ -262,13 +381,133 @@ PortalDrop(Portal portal)
  * references...
  */
 void
-AtEOXact_portals(void)
+AtCommit_Portals(void)
 {
        HASH_SEQ_STATUS status;
        PortalHashEnt *hentry;
+       TransactionId xact = GetCurrentTransactionId();
 
        hash_seq_init(&status, PortalHashTable);
 
        while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
-               PortalDrop(hentry->portal);
+       {
+               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.
+                */
+               if (portal->portalActive)
+                       continue;
+
+               if (portal->cursorOptions & CURSOR_OPT_HOLD)
+               {
+                       /*
+                        * Do nothing to cursors held over from a previous
+                        * transaction.
+                        */
+                       if (portal->createXact != xact)
+                               continue;
+
+                       /*
+                        * 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.
+                        */
+                       PortalCreateHoldStore(portal);
+                       PersistHoldablePortal(portal);
+               }
+               else
+               {
+                       /* Zap all non-holdable portals */
+                       PortalDrop(portal, false);
+               }
+       }
+}
+
+/*
+ * Abort processing for portals.
+ *
+ * At this point we reset the "active" flags and run the cleanup hook if
+ * present, but we can't release 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.  Also, we
+ * want to run the cleanup hook now to be certain it knows that we had an
+ * error abort and not successful conclusion.
+ */
+void
+AtAbort_Portals(void)
+{
+       HASH_SEQ_STATUS status;
+       PortalHashEnt *hentry;
+       TransactionId xact = GetCurrentTransactionId();
+
+       hash_seq_init(&status, PortalHashTable);
+
+       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       {
+               Portal          portal = hentry->portal;
+
+               portal->portalActive = false;
+
+               /*
+                * 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;
+
+               /* let portalcmds.c clean up the state it knows about */
+               if (PointerIsValid(portal->cleanup))
+               {
+                       (*portal->cleanup) (portal, true);
+                       portal->cleanup = NULL;
+               }
+       }
+}
+
+/*
+ * Post-abort cleanup for portals.
+ *
+ * Delete all portals not held over from prior transactions.
+ */
+void
+AtCleanup_Portals(void)
+{
+       HASH_SEQ_STATUS status;
+       PortalHashEnt *hentry;
+       TransactionId xact = GetCurrentTransactionId();
+
+       hash_seq_init(&status, PortalHashTable);
+
+       while ((hentry = (PortalHashEnt *) hash_seq_search(&status)) != NULL)
+       {
+               Portal          portal = hentry->portal;
+
+               /*
+                * Let's just make sure no one's active...
+                */
+               portal->portalActive = false;
+
+               /*
+                * 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;
+
+               /* Else zap it with prejudice. */
+               PortalDrop(portal, true);
+       }
 }